<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dave Gordon</title>
    <description>The latest articles on DEV Community by Dave Gordon (@dave_gordon).</description>
    <link>https://dev.to/dave_gordon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4082771%2Fcf7b9dda-01ec-46e2-be67-3aff920fc302.png</url>
      <title>DEV Community: Dave Gordon</title>
      <link>https://dev.to/dave_gordon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dave_gordon"/>
    <language>en</language>
    <item>
      <title>8 React Native Video Editor Options and the Code You Inherit</title>
      <dc:creator>Dave Gordon</dc:creator>
      <pubDate>Fri, 23 Oct 2026 12:54:40 +0000</pubDate>
      <link>https://dev.to/dave_gordon/8-react-native-video-editor-options-and-the-code-you-inherit-2ekk</link>
      <guid>https://dev.to/dave_gordon/8-react-native-video-editor-options-and-the-code-you-inherit-2ekk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgdrkuwzhxln459xrupt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgdrkuwzhxln459xrupt.jpeg" alt="A developer watches a tablet showing a multi-track video editor, with the React Native logo beside it." width="799" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In React Native the editing runs in native code, so what you pick is the bridge layer.&lt;/li&gt;
&lt;li&gt;Four of the eight publish a current React Native package. Only two of those declare a codegen spec, and one of the three without a spec shipped this month.&lt;/li&gt;
&lt;li&gt;Two never run on the device: both render on a server, and the one browser preview among them is desktop-only.&lt;/li&gt;
&lt;li&gt;FFmpegKit is retired. Repository archived 2 July 2026, last React Native release September 2023.&lt;/li&gt;
&lt;li&gt;Package-and-docs screen, no device runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are picking a React Native video editor, start with the layer you will actually own: the JavaScript package and its native bridge.&lt;/p&gt;

&lt;p&gt;Read who publishes it, under which license, whether it declares a codegen spec, when it last shipped, and where the editing work runs. Those five answers narrow the field faster than any feature list.&lt;/p&gt;

&lt;p&gt;This is the screening pass. I read packages and documentation to decide which options are worth putting on a device at all. The device measurements, with real export runs, come in a later post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where these facts come from
&lt;/h2&gt;

&lt;p&gt;I went through the npm metadata for each package: the license field, the codegenConfig block, the published date, and the peer and dev dependency pins. Then each project's own documentation. Everything below was read in October 2026 unless a sentence says otherwise.&lt;/p&gt;

&lt;p&gt;You can redo the package half of this in one line, which is the whole reason the screen is built on metadata rather than on feature lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm view react-native-video-trim version license time.modified codegenConfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prints checks 1, 2, and 3 for any package in this post, and for any package that gets published after it. The numbers below will age. The command will not.&lt;/p&gt;

&lt;p&gt;Nothing here was installed or run. I did no exports, and I timed nothing, so there is no device, no OS version, no chipset, and no per-device result anywhere in this post.&lt;/p&gt;

&lt;p&gt;Export timings for these SDKs do exist, published as benchmarks by the companies selling them. I produced none of them.&lt;/p&gt;

&lt;p&gt;For anything that is not on npm, the vendor documentation is the source, and a documentation page is a snapshot of whenever someone last looked. Before any of it decides a build, get the claim from the vendor in a document with a date and a name on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 1: Who publishes the React Native package, and what license does it carry?
&lt;/h2&gt;

&lt;p&gt;Look at the npm author field, the maintainer list, and the license field.&lt;/p&gt;

&lt;p&gt;An author that is a company means a support contract is possible and a bill is coming. A single named maintainer means your escalation path is one person's inbox.&lt;/p&gt;

&lt;p&gt;IMG.LY publishes &lt;a class="mentioned-user" href="https://dev.to/imgly"&gt;@imgly&lt;/a&gt;/editor-react-native under the author "IMG.LY GmbH", maintained by company accounts rather than one individual. Its license field reads "SEE LICENSE IN LICENSE.md", which is a commercial license. That is a complete first-party React Native story: the vendor writes the bridge and signs for it.&lt;/p&gt;

&lt;p&gt;Licensing is per target platform for React Native or Flutter, monthly or yearly, with one license covering several app identifiers such as staging, beta, and production, as &lt;a href="https://img.ly/products/video-sdk/" rel="noopener noreferrer"&gt;IMG.LY's video SDK page&lt;/a&gt; and its pricing page read. Access is commercial and sales-led, full white-labeling is offered, and enterprise terms include an air-gapped option.&lt;/p&gt;

&lt;p&gt;Those two pages are the one exception to the October reading above. I last went through them in June, so of everything here, they are the most likely to have moved.&lt;/p&gt;

&lt;p&gt;Banuba publishes video-editor-react-native, authored by Banuba, against the repository at &lt;a href="https://github.com/Banuba/ve-sdk-react-native" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, with one company maintainer account.&lt;/p&gt;

&lt;p&gt;Its npm license field reads MIT, and that one needs reading slowly. The SDK is commercial and will not initialise without a token, so the MIT covers the bridge code in the package and not the engine underneath it. IMG.LY's field says "SEE LICENSE IN LICENSE.md" and tells you that in the field itself. If you run check 1 the way I just described it, on this listing you get the wrong answer.&lt;/p&gt;

&lt;p&gt;react-native-video-trim is MIT, authored by Duc Trung Mai, with one maintainer on the package.&lt;/p&gt;

&lt;p&gt;MIT is the cleanest license outcome in this set: no per-platform fee, no approval gate, and the whole module is small enough to read end to end, since &lt;a href="https://github.com/maitrungduc1410/react-native-video-trim" rel="noopener noreferrer"&gt;its repository&lt;/a&gt; scopes itself as "Video trimmer for your React Native app".&lt;/p&gt;

&lt;p&gt;react-native-video-processing is also MIT, authored by Shahen Hovhannisyan, one maintainer, described as "Native Video processing/editing for React-Native" on &lt;a href="https://github.com/shahen94/react-native-video-processing" rel="noopener noreferrer"&gt;its repository&lt;/a&gt;. MIT plus a dormant codebase means you own the fork outright, with no license conversation to have.&lt;/p&gt;

&lt;p&gt;FFmpegKit's React Native library is LGPL v3.0 by default, and GPL v3.0 when the build enables GPL-licensed libraries, through the --enable-gpl flag or a prebuilt package with a -gpl suffix, as &lt;a href="https://github.com/arthenica/ffmpeg-kit" rel="noopener noreferrer"&gt;its repository&lt;/a&gt; documents.&lt;/p&gt;

&lt;p&gt;Its own patents section states the belief that FFmpeg, kvazaar, x264 and x265 include algorithms subject to software patents, and recommends legal advice. It also gives format coverage no vendor SDK in this group matches, with no license fee attached.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://shotstack.io/docs/guide/studio-sdk/" rel="noopener noreferrer"&gt;Shotstack's Studio SDK&lt;/a&gt; is published as @shotstack/shotstack-studio under PolyForm Shield 1.0.0, which Shotstack describes as usable "for any application, commercial or non-commercial, as long as it doesn't directly compete with Shotstack".&lt;/p&gt;

&lt;p&gt;That is a source-available license. It is not an open-source license, and the distinction decides things if your own product is a video tool.&lt;/p&gt;

&lt;p&gt;Creatomate publishes &lt;a href="https://creatomate.com/docs/api/preview-sdk/installation" rel="noopener noreferrer"&gt;@creatomate/preview&lt;/a&gt; at version 1.6.1, published 3 September 2026, under MIT, with devDependencies only and no runtime dependencies at all.&lt;/p&gt;

&lt;p&gt;For a browser bundle that is the lowest dependency surface in this group, and MIT on the client library is worth noting next to a cloud service you pay for by output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.meishesdk.com/editsdk/" rel="noopener noreferrer"&gt;Meishe&lt;/a&gt; publishes no React Native package at all, so there is nothing to read for checks 1 to 3.&lt;/p&gt;

&lt;p&gt;Its page title reads "Video Editor SDK for React Native &amp;amp; Flutter" and the body states that it "supports React Native, Flutter and other high-quality application development frameworks". No package, no installation page and no sample exists on the site or the docs.&lt;/p&gt;

&lt;p&gt;Pricing is not publicly documented, which puts access somewhere between approval-gated and enterprise-only without saying which.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 2: Is there a codegen spec, or is it still on the old bridge?
&lt;/h2&gt;

&lt;p&gt;Open the package and search for codegenConfig.&lt;/p&gt;

&lt;p&gt;Its presence means the module was built as a turbo-module against the New Architecture. Its absence means an old-architecture native module, and a migration that lands on you.&lt;/p&gt;

&lt;p&gt;IMG.LY ships a codegenConfig block with the spec name IMGLYEditorModuleSpec and the Android package ly.img.editor.reactnative.module. react-native-video-trim declares spec VideoTrimSpec with Android package com.videotrim and was scaffolded as a turbo-module. Those are the two New Architecture entries here.&lt;/p&gt;

&lt;p&gt;Banuba's package has no codegenConfig. Its peer dependencies accept any React Native version, and its devDependencies pin react-native 0.83.0 with react 19.2.0, which is the newest pair anyone in this group builds against.&lt;/p&gt;

&lt;p&gt;That combination is worth sitting with: current on the React Native version, old on the architecture. You get a package built against this autumn's React Native and an interop path you maintain yourself.&lt;/p&gt;

&lt;p&gt;react-native-video-processing has no codegenConfig either. Treat it as a legacy bridge module and budget the port before you budget features.&lt;/p&gt;

&lt;p&gt;For FFmpegKit, the React Native library provided a JavaScript API with TypeScript definitions, and nothing in the archived repository addresses codegen. Shotstack and Creatomate publish browser JavaScript, so this check does not apply to either, and Meishe publishes nothing to check.&lt;/p&gt;

&lt;p&gt;The same pattern shows up on the other side of the cross-platform fence, which I went through in &lt;a href="https://medium.com/@davegord86/the-flutter-video-editor-sdk-problem-nobody-talks-about-c21ab318f74b" rel="noopener noreferrer"&gt;a post on Flutter video editor packages wrapping native engines&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 3: When was the React Native package last published, and against which React Native version?
&lt;/h2&gt;

&lt;p&gt;Read the published date, then read the dev dependency pins. The pins tell you which React Native the maintainer last built against, which is a better signal than a README badge.&lt;/p&gt;

&lt;p&gt;IMG.LY's package is at version 1.84.0, published 15 October 2026. react-native-video-trim is at 8.2.4, published 5 October 2026, with devDependencies pinning react-native 0.79.2 and react 19.0.0. Both are current enough that your own upgrade cadence sets the pace.&lt;/p&gt;

&lt;p&gt;Banuba's package is at 0.55.2, published 9 October 2026. The four releases before it land on 6 October, 12 September, 8 September, and 18 August, which is the most regular cadence of anything in this group.&lt;/p&gt;

&lt;p&gt;The version number deserves a glance of its own. A 0.x on a product that has been selling for years usually means the package is much newer than the engine behind it, and that the bridge is where the churn is.&lt;/p&gt;

&lt;p&gt;react-native-video-processing is at 1.7.2, published 8 September 2022, pinning react-native ^0.49.5 and react 16.0.0-beta.5. Four years of React Native releases have landed since.&lt;/p&gt;

&lt;p&gt;Beyond that package metadata the project publishes very little, so the version gap is most of what there is to read on it, and I would rather say that than score it down quietly.&lt;/p&gt;

&lt;p&gt;FFmpegKit's last React Native release in the repository's own version table is 6.0.2, built on FFmpeg 6.0, released 19 September 2023. Shotstack, Creatomate and Meishe publish no React Native package, so there is no date to read for any of the three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 4: Does the editing happen on the device or on someone's server?
&lt;/h2&gt;

&lt;p&gt;This check decides your architecture, your offline behavior and where your users' footage goes. Read whether the project documents on-device processing, a hosted render path, or both.&lt;/p&gt;

&lt;p&gt;IMG.LY runs client-side, and IMG.LY also publishes a Node.js package that runs the same engine headlessly on a server for batch rendering. That headless server path is the only documented one of its kind among these eight, which matters when you need the same output from a phone and from a queue.&lt;/p&gt;

&lt;p&gt;Banuba runs on device, and its package description states that it works offline. No server render path is published alongside it, so if you later need the same project rendered in a queue, that is a second engine rather than a flag.&lt;/p&gt;

&lt;p&gt;Shotstack is the opposite shape. There is no React Native SDK, no Flutter SDK, no Unity SDK, no native iOS or Android SDK and no on-device processing path.&lt;/p&gt;

&lt;p&gt;What exists is the Edit API: hosted, JSON in and rendered video out, priced per minute of output, with server-side client libraries for Node.js, Python, PHP and Ruby, plus the browser Studio SDK whose export path uses FFmpeg.&lt;/p&gt;

&lt;p&gt;Taking the whole render off the device is the point of it, and the same Edit JSON the browser editor loads is what the Edit API renders. Sign-up is open and the per-minute price is published.&lt;/p&gt;

&lt;p&gt;Creatomate splits the same way, and its own Preview SDK installation page is direct about why. The preview "renders video in real time on the user's device, which takes more performance than most phones and tablets have. It therefore runs in desktop browsers only", best on Chrome, Edge and Opera, with Safari and Firefox supported and playback possibly less smooth.&lt;/p&gt;

&lt;p&gt;The page tells you to detect the device before loading the SDK and, for phones and tablets, to "show them a video rendered through the API instead of the live preview", at low resolution through a render_scale parameter. On the final file: "Rendering an MP4 in the browser is too unreliable across devices." No React Native path is published.&lt;/p&gt;

&lt;p&gt;The strength here is consistency: the same template and the same changes drive both the preview and the server render, so what the user sees is what the API produces. Sign-up is open.&lt;/p&gt;

&lt;p&gt;FFmpegKit, react-native-video-trim and react-native-video-processing are all on-device by construction, and all three install from npm with no approval step.&lt;/p&gt;

&lt;p&gt;Meishe publishes the widest platform list of the eight: iOS, Android, Web, PC and HarmonyOS Next, with offline SDK operation and a privatized deployment option, plus 8K image processing, HDR export and multi-track editing.&lt;/p&gt;

&lt;p&gt;The documented integration path is an Objective-C API and a Java API against a class called NvsStreamingContext, and a web build loaded from a CDN script tag rather than an npm package. The SDK is split into EffectSDK and StreamingSDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 5: What happens if the package stops being maintained?
&lt;/h2&gt;

&lt;p&gt;FFmpegKit is the worked case, because it already happened.&lt;/p&gt;

&lt;p&gt;The arthenica/ffmpeg-kit repository page states: "This repository was archived by the owner on Jul 2, 2026. It is now read-only." The README's "Update (July 2026)" section states that "FFmpegKit has been officially retired" and that development continues as FFmpegKitNext, which "is distributed as source only".&lt;/p&gt;

&lt;p&gt;Read what that sentence costs you. Source-only distribution means you build the native binaries for both platforms yourself, in CI, for every FFmpeg configuration flag your feature set needs, and you keep doing it.&lt;/p&gt;

&lt;p&gt;The archived README now points readers to community-maintained forks through the package managers, so your other option is trusting a fork whose maintainer you have not met. Both paths start at &lt;a href="https://github.com/arthenica/ffmpeg-kit-next" rel="noopener noreferrer"&gt;the successor repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ask the same question of every other option before you commit, because the answer differs by class.&lt;/p&gt;

&lt;p&gt;A commercial package with company maintainers, which covers IMG.LY, Banuba and Meishe, puts you in a contract conversation. That is the easiest of these to plan for.&lt;/p&gt;

&lt;p&gt;An MIT package with a single maintainer leaves you holding a fork. Survivable while the scope is a trimmer, expensive once the scope is a full editor.&lt;/p&gt;

&lt;p&gt;The cloud APIs expose you to an outage you cannot patch, and eventually to migrating your JSON format somewhere else.&lt;/p&gt;

&lt;p&gt;With a source-available license your legal reviewer inherits the question of what "directly compete" covers for your product, and that review is worth starting before the integration rather than after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;RN package and license&lt;/th&gt;
&lt;th&gt;Codegen&lt;/th&gt;
&lt;th&gt;Last RN publish&lt;/th&gt;
&lt;th&gt;Where processing runs&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IMG.LY CreativeEditor SDK&lt;/td&gt;
&lt;td&gt;Vendor SDK with an RN package&lt;/td&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/imgly"&gt;@imgly&lt;/a&gt;/editor-react-native, "SEE LICENSE IN LICENSE.md" (commercial)&lt;/td&gt;
&lt;td&gt;Yes, IMGLYEditorModuleSpec&lt;/td&gt;
&lt;td&gt;1.84.0, 15 Oct 2026&lt;/td&gt;
&lt;td&gt;On device, plus a headless Node.js server path&lt;/td&gt;
&lt;td&gt;Commercial, per target platform, white-labeling and air-gapped option&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Banuba Video Editor SDK&lt;/td&gt;
&lt;td&gt;Vendor SDK with an RN package&lt;/td&gt;
&lt;td&gt;video-editor-react-native, npm field reads MIT, engine is commercial&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;0.55.2, 9 Oct 2026&lt;/td&gt;
&lt;td&gt;On device, offline, no server path published&lt;/td&gt;
&lt;td&gt;Commercial, token required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meishe SDK&lt;/td&gt;
&lt;td&gt;Vendor SDK, native only&lt;/td&gt;
&lt;td&gt;None published&lt;/td&gt;
&lt;td&gt;None to read&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;On device, offline operation, privatized deployment&lt;/td&gt;
&lt;td&gt;Pricing not publicly documented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FFmpegKit&lt;/td&gt;
&lt;td&gt;Community RN module, retired&lt;/td&gt;
&lt;td&gt;ffmpeg-kit-react-native, LGPL v3.0, GPL v3.0 with GPL builds&lt;/td&gt;
&lt;td&gt;Not addressed&lt;/td&gt;
&lt;td&gt;6.0.2, 19 Sep 2023&lt;/td&gt;
&lt;td&gt;On device&lt;/td&gt;
&lt;td&gt;Open, archived 2 Jul 2026, successor is source only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-video-trim&lt;/td&gt;
&lt;td&gt;Community RN module&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Yes, VideoTrimSpec&lt;/td&gt;
&lt;td&gt;8.2.4, 5 Oct 2026&lt;/td&gt;
&lt;td&gt;On device&lt;/td&gt;
&lt;td&gt;Open, npm install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;react-native-video-processing&lt;/td&gt;
&lt;td&gt;Community RN module&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;1.7.2, 8 Sep 2022&lt;/td&gt;
&lt;td&gt;On device&lt;/td&gt;
&lt;td&gt;Open, npm install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shotstack&lt;/td&gt;
&lt;td&gt;Cloud render API plus browser editor&lt;/td&gt;
&lt;td&gt;No RN package; @shotstack/shotstack-studio is PolyForm Shield 1.0.0 (source-available)&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Server, priced per minute of output&lt;/td&gt;
&lt;td&gt;Open sign-up, price published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creatomate&lt;/td&gt;
&lt;td&gt;Cloud render API plus browser preview&lt;/td&gt;
&lt;td&gt;No RN package; @creatomate/preview (MIT) for desktop browsers&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Server, preview in desktop browser only&lt;/td&gt;
&lt;td&gt;Open sign-up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row in that table is stated in the text above, so nothing lives only in the table.&lt;/p&gt;

&lt;p&gt;These are the values each project published about itself when I read them. I measured none of them, and the npm command above re-reads four of these columns for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which React Native video editor would I start with?
&lt;/h2&gt;

&lt;p&gt;Feature depth is a separate question from maintenance, and I went through the feature side of these tools in &lt;a href="https://medium.com/@davegord86/top-video-editor-sdks-for-mobile-apps-in-2026-feature-showdown-df8a8b1971b2" rel="noopener noreferrer"&gt;an earlier roundup of mobile video editor SDKs&lt;/a&gt;. What follows is the maintenance answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A short-form camera app where users trim clips before upload:&lt;/strong&gt; start with react-native-video-trim. MIT plus a current New Architecture turbo-module plus a scope you can read in one sitting means the fork risk is bounded even with one maintainer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A multi-track editor inside a consumer app, with a vendor who signs for the bridge:&lt;/strong&gt; shortlist IMG.LY and Banuba, and let the architecture split them.&lt;/p&gt;

&lt;p&gt;IMG.LY declares a codegen spec and publishes a headless Node.js path, so the New Architecture is handled and the same project can render server-side later. Banuba ships more often, builds against a newer React Native, and runs offline, but has no codegen spec and no server render, so you carry the interop path and you keep rendering on the phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A template-driven product where users fill in text and media and a finished video comes out:&lt;/strong&gt; start with Shotstack or Creatomate. The render never touches the phone, both bill against output, and your React Native app only posts JSON and polls for a URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fixed FFmpeg pipeline you already understand, with CI engineers to hand:&lt;/strong&gt; start from the FFmpegKitNext source. You accept a build burden in exchange for format coverage and no license fee, having read the patents note first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HarmonyOS Next or desktop alongside iOS and Android:&lt;/strong&gt; start a conversation with Meishe, and go in expecting to write the React Native bridge yourself against NvsStreamingContext.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two packages pass all five checks, and both shipped in October
&lt;/h2&gt;

&lt;p&gt;The screen sorts these options by what each project publishes about its React Native layer.&lt;/p&gt;

&lt;p&gt;IMG.LY and react-native-video-trim give you a current turbo-module to read. Banuba gives you the most frequent releases and the newest React Native pin, with no codegen spec behind them. react-native-video-processing gives you MIT source and a four-year-old bridge.&lt;/p&gt;

&lt;p&gt;FFmpegKit gives you an archive notice and a source-only successor. Meishe names React Native in its page title and publishes nothing for it. Shotstack and Creatomate move the work to a server, which changes your architecture more than your feature list.&lt;/p&gt;

&lt;p&gt;Before the device post, the next thing I open is node_modules for the two packages that declare a codegen spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions I get asked about this
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does a missing codegenConfig mean the package will not work on the New Architecture?
&lt;/h3&gt;

&lt;p&gt;It can still work through the interop layer, but you are relying on a compatibility path rather than a generated spec, and nobody upstream is testing your case.&lt;/p&gt;

&lt;p&gt;For react-native-video-processing, with react-native pinned at ^0.49.5, assume you are the one maintaining that path. For Banuba, pinned at 0.83.0, the package is current enough that the interop path is at least being exercised against a recent React Native.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpegKit's last release and deal with it later?
&lt;/h3&gt;

&lt;p&gt;You can install 6.0.2 from September 2023 today, and it will keep building until something in your toolchain moves.&lt;/p&gt;

&lt;p&gt;The cost arrives on the next iOS or Android SDK bump, when no upstream release exists and you are either patching the archive yourself or moving to FFmpegKitNext and its source-only builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is PolyForm Shield 1.0.0 open source?
&lt;/h3&gt;

&lt;p&gt;No, it is source-available. You can read and use the code, including commercially, under Shotstack's stated condition that your application does not directly compete with Shotstack.&lt;/p&gt;

&lt;p&gt;That condition is the part to send to your legal reviewer before you build on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does an npm package say MIT when the SDK costs money?
&lt;/h3&gt;

&lt;p&gt;Because the field describes the package, not the engine it loads. A vendor bridge can be MIT while the native binaries behind it need a commercial token, and the npm field has no way to say so.&lt;/p&gt;

&lt;p&gt;IMG.LY handles this by putting "SEE LICENSE IN LICENSE.md" in the field. When a vendor package shows a bare MIT, check whether the thing you are licensing is the wrapper or the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not just use a cloud API and skip native code entirely?
&lt;/h3&gt;

&lt;p&gt;That is a real option if your editing is template-driven and your users tolerate a round trip, which is how Shotstack's Edit API and Creatomate's REST API are shaped.&lt;/p&gt;

&lt;p&gt;It stops being an option when you need frame-accurate scrubbing with no network, since Creatomate's installation page puts the Preview SDK in desktop browsers only and Shotstack documents no on-device path.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>video</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Flutter AR Plugins in 2026: 7 Packages Screened Before a Device Test</title>
      <dc:creator>Dave Gordon</dc:creator>
      <pubDate>Thu, 22 Oct 2026 08:50:42 +0000</pubDate>
      <link>https://dev.to/dave_gordon/flutter-ar-plugins-in-2026-7-packages-screened-before-a-device-test-361a</link>
      <guid>https://dev.to/dave_gordon/flutter-ar-plugins-in-2026-7-packages-screened-before-a-device-test-361a</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgukc19itoikttzgcteb4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgukc19itoikttzgcteb4.jpeg" alt="A developer frowns over a thick manual labelled " width="799" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seven Flutter AR packages from pub.dev, read in October 2026. No builds and no device runs.&lt;/li&gt;
&lt;li&gt;Three track faces only. Four wrap ARKit or ARCore and track the room, and three of those four also expose a face mode.&lt;/li&gt;
&lt;li&gt;None of the seven gives you face tracking in a Flutter web build.&lt;/li&gt;
&lt;li&gt;deepar_flutter is DeepAR's own package, last uploaded in January 2023, and its permission_handler pin clashes with apps already on version 11 or newer.&lt;/li&gt;
&lt;li&gt;The ML Kit face mesh package carries an iOS tag on pub.dev, while its README says Android only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flutter AR always runs in native code. ARKit, ARCore, Google's ML Kit or a vendor SDK does the tracking, and the Dart package you add to pubspec.yaml is the bridge to it, so choosing a Flutter AR package mostly means choosing which native engine you inherit and how much setup it leaves on your desk.&lt;/p&gt;

&lt;p&gt;I screen for that before any package earns time on a phone. This post is the screen for seven packages, run as five numbered checks. The native SDKs underneath are a separate question, and I went through six of those from their own documentation in &lt;a href="https://medium.com/@davegord86/face-ar-sdks-compared-2c14c86595c6" rel="noopener noreferrer"&gt;6 face AR SDKs compared by what their docs publish&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I ran this screen
&lt;/h2&gt;

&lt;p&gt;Sources for this round, read in October 2026: each package's pub.dev listing (version, upload date, publisher, license, platform tags, dependencies), its README, and the API reference where the README was thin. For two native face SDKs that skip pub.dev, Agora's integration guide and each vendor's own site.&lt;/p&gt;

&lt;p&gt;Nothing was built, installed or run for this post, so it carries no device model, OS version, chipset or frame time. It is a filter that decides what deserves device time, and the device round is a separate post.&lt;/p&gt;

&lt;p&gt;One rule for this round was self-serve only. A package qualified if a developer can install it and initialize it without talking to a salesperson. DeepAR's license keys come from a project you create on its developer site, so it qualified. Three vendor SDKs did not, and they are at the end.&lt;/p&gt;

&lt;p&gt;If one of these facts ends up deciding your build, confirm it with the publisher in writing, because a pub.dev listing changes with every upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seven packages
&lt;/h2&gt;

&lt;p&gt;Four wrap a platform AR framework and are built around the room: &lt;a href="https://pub.dev/packages/ar_flutter_plugin_plus" rel="noopener noreferrer"&gt;ar_flutter_plugin_plus&lt;/a&gt;, &lt;a href="https://pub.dev/packages/arkit_plugin" rel="noopener noreferrer"&gt;arkit_plugin&lt;/a&gt;, &lt;a href="https://pub.dev/packages/arcore_flutter_plugin" rel="noopener noreferrer"&gt;arcore_flutter_plugin&lt;/a&gt; and &lt;a href="https://pub.dev/packages/augen" rel="noopener noreferrer"&gt;augen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Three track faces only: &lt;a href="https://pub.dev/packages/google_mlkit_face_detection" rel="noopener noreferrer"&gt;google_mlkit_face_detection&lt;/a&gt;, &lt;a href="https://pub.dev/packages/google_mlkit_face_mesh_detection" rel="noopener noreferrer"&gt;google_mlkit_face_mesh_detection&lt;/a&gt; and &lt;a href="https://pub.dev/packages/deepar_flutter" rel="noopener noreferrer"&gt;deepar_flutter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 1: does the package track a face, or the room around you?
&lt;/h2&gt;

&lt;p&gt;This is the split people skip, and it decides how much rendering code you write.&lt;/p&gt;

&lt;p&gt;ar_flutter_plugin_plus wraps ARKit on iOS and ARCore on Android. Its eight example apps cover debug options, local and online objects, anchors on planes, transformation gestures, screenshots, cloud anchors, external model management and image tracking. None of the eight is a face sample, so treat it as a world-tracking package.&lt;/p&gt;

&lt;p&gt;arkit_plugin wraps ARKit and lists a Face Tracking example, described as a face mask sample, next to plane detection, image detection and body tracking.&lt;/p&gt;

&lt;p&gt;arcore_flutter_plugin wraps ARCore on Android and ships an ArCoreFaceView widget. Its enableAugmentedFaces flag defaults to false, so face mode is something you switch on.&lt;/p&gt;

&lt;p&gt;augen wraps ARCore on Android and RealityKit with ARKit on iOS. Its feature list puts face tracking "with facial landmarks and expressions" beside plane detection, image tracking, cloud anchors and physics.&lt;/p&gt;

&lt;p&gt;The other three only do faces, and they split again. google_mlkit_face_detection returns a bounding box, head rotation angles, landmarks for the mouth, ears, eyes, cheeks and nose, a smile probability when classification is on, and a tracking ID when tracking is on. google_mlkit_face_mesh_detection returns a 468-point 3D mesh. Both hand geometry back to Dart, and drawing an effect on top of it is your job.&lt;/p&gt;

&lt;p&gt;deepar_flutter sits at the other end. Its feature list is live AR preview, screenshots, video recording, camera flip and flash, so the vendor's renderer draws the effect and you place a preview widget.&lt;/p&gt;

&lt;p&gt;Here is the ML Kit side in practice, adapted from the face mesh package's README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// FaceMeshDetector and FaceMesh come from google_mlkit_face_mesh_detection.&lt;/span&gt;
&lt;span class="c1"&gt;// inputImage: one camera frame wrapped as an InputImage (google_mlkit_commons).&lt;/span&gt;
&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;readFaceMesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InputImage&lt;/span&gt; &lt;span class="n"&gt;inputImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;meshDetector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;FaceMeshDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;option:&lt;/span&gt; &lt;span class="n"&gt;FaceMeshDetectorOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;faceMesh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FaceMesh&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;meshes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;meshDetector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;FaceMesh&lt;/span&gt; &lt;span class="n"&gt;mesh&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;meshes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mesh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;points&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// the 468 3D points&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;triangles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mesh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;triangles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// topology for your own renderer&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;meshDetector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every point and triangle in that loop is input for a painter or shader you still have to write.&lt;/p&gt;

&lt;p&gt;One number to keep straight if you are comparing docs: ML Kit's face mesh returns 468 points, while Google's MediaPipe Face Landmarker returns 478. They are different products from the same company, and the counts are not a typo in either place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 2: who publishes it, under which license, and when did it last ship?
&lt;/h2&gt;

&lt;p&gt;pub.dev shows a verified publisher as a domain next to the package name. Six of the seven have one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ar_flutter_plugin_plus: xinix.tech, version 1.1.3, uploaded 28 January 2026. It is a fork of Lars Carius's original &lt;a href="https://pub.dev/packages/ar_flutter_plugin" rel="noopener noreferrer"&gt;ar_flutter_plugin&lt;/a&gt;, whose newest release, 0.7.3, dates from 19 November 2022.&lt;/li&gt;
&lt;li&gt;arkit_plugin: leushchenko.com, version 1.5.0, uploaded 3 August 2026.&lt;/li&gt;
&lt;li&gt;arcore_flutter_plugin: gdifrancesco.dev, version 0.1.0, uploaded 7 February 2023, plus a 0.2.0-alpha prerelease from 20 February 2023.&lt;/li&gt;
&lt;li&gt;google_mlkit_face_detection 0.15.1 and google_mlkit_face_mesh_detection 0.6.1: flutter-ml.dev, both uploaded 17 August 2026. Both READMEs state that the plugin is "not sponsored or maintained by Google".&lt;/li&gt;
&lt;li&gt;deepar_flutter: deepar.ai, version 0.0.5, uploaded 29 January 2023.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;augen is the one unverified uploader in the set, and also the newest upload, version 1.5.0 from 1 October 2026.&lt;/p&gt;

&lt;p&gt;All seven list MIT on pub.dev. That license covers the bridge code in each package. The engine underneath is a separate dependency, and DeepAR's will not initialize without a license key for each platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does DeepAR support Flutter?
&lt;/h3&gt;

&lt;p&gt;Yes, through a package DeepAR publishes itself. pub.dev lists deepar_flutter under the verified publisher deepar.ai with the description "Official Flutter SDK for DeepAR", for Android SDK 23+ and iOS 13.0+. The catch is age: 0.0.5, uploaded 29 January 2023, is the newest of its five versions.&lt;/p&gt;

&lt;p&gt;The confusion has a source. DeepAR's documentation, as it read on 17 October 2026, names four supported platforms (iOS, macOS, Android and Web) and has no Flutter page, so research that stops at the docs comes back with the wrong answer.&lt;/p&gt;

&lt;p&gt;The age shows up in the dependency list. deepar_flutter 0.0.5 depends on permission_handler ^10.0.0, which in &lt;a href="https://dart.dev/tools/pub/dependencies" rel="noopener noreferrer"&gt;Dart's caret syntax&lt;/a&gt; means any version from 10.0.0 up to, but not including, 11.0.0. If your app or another package in it needs permission_handler 11 or newer, pub cannot resolve both without a dependency_overrides entry, and the same Dart page warns that overriding outside a package's declared range "might break your application". ar_flutter_plugin_plus 1.1.3, for example, asks for permission_handler ^12.0.1, so those two packages cannot sit in one pubspec without an override.&lt;/p&gt;

&lt;p&gt;Access is self-serve. You create a project on DeepAR's developer site and generate the keys there. The native SDK is free for development and testing with a watermark. DeepAR's pricing page, last checked on 17 October 2026, listed free use up to 10 monthly active users, paid tiers from $25 a month for up to 1,000 MAU to $1,000 a month for up to 100,000 MAU, and sales above that. Treat those figures as approximate, not a quote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 3: does the pub.dev platform tag match what actually works?
&lt;/h2&gt;

&lt;p&gt;Read the tags and the README together, because they do not always agree.&lt;/p&gt;

&lt;p&gt;google_mlkit_face_mesh_detection is tagged Android and iOS. Its README opens with a note that the feature "is still in Beta, and it is only available for Android".&lt;/p&gt;

&lt;p&gt;augen is tagged Android, iOS and web. Its web table shows marker tracking from PNG or JPG templates working in the browser, while face tracking, plane detection and cloud anchors are marked "Mobile only". On iOS it adds one more limit: RealityKit cannot read GLTF or GLB, so those models render as an orange placeholder box and the README tells you to ship USDZ for iOS.&lt;/p&gt;

&lt;p&gt;arkit_plugin is iOS only, and its FAQ answers the Android question with "No, as ARKit is not available on Android". arcore_flutter_plugin is Android only.&lt;/p&gt;

&lt;p&gt;google_mlkit_face_detection covers Android and iOS, and its README states that web "or any other platform is not supported". deepar_flutter lists "Android &amp;amp; iOS", even though DeepAR's native SDK also runs on the web.&lt;/p&gt;

&lt;p&gt;Put together, none of the seven gives a Flutter web build face tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use ML Kit face mesh detection on iOS in Flutter?
&lt;/h3&gt;

&lt;p&gt;Not with google_mlkit_face_mesh_detection 0.6.1, whose README limits the feature to Android while it is in Beta. On iOS the closest option in this set is google_mlkit_face_detection, which gives you contours and landmarks but no 468-point mesh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 4: what do you set up outside pubspec.yaml?
&lt;/h2&gt;

&lt;p&gt;None of the following happens on flutter pub get, so it belongs in the estimate.&lt;/p&gt;

&lt;p&gt;deepar_flutter on Android needs compileSdkVersion 33 or more and minSdkVersion 23 or more, plus the native library itself: you download it from DeepAR and place it at android/app/libs/deepar.aar. Its README adds ProGuard keep rules, "else app might crash in release build". On iOS it wants a 13.0 deployment target and Podfile preprocessor definitions for camera and microphone permissions.&lt;/p&gt;

&lt;p&gt;google_mlkit_face_detection on iOS needs a 15.5 minimum deployment target, Xcode 15.3.0 or newer, and armv7 excluded in Xcode, because ML Kit supports 64-bit architectures only.&lt;/p&gt;

&lt;p&gt;augen on Android declares android.hardware.camera.ar as required and sets the com.google.ar.core meta-data to required, with minSdkVersion 24. On iOS it needs NSCameraUsageDescription, arkit under UIRequiredDeviceCapabilities, and a 13.0 deployment target. It also asks for Dart 3.9.2 or newer, which matters if your app is pinned to an older toolchain.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I turn on face tracking in arkit_plugin?
&lt;/h3&gt;

&lt;p&gt;You opt in, because the TrueDepth and face tracking code is not compiled in by default. On Flutter 3.44 and later, which builds plugins with Swift Package Manager, add this key to ios/Runner/Info.plist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ARKitFaceTrackingEnabled&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then clear the SwiftPM manifest cache and DerivedData:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/Library/Caches/org.swift.swiftpm/manifests
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/Library/Developer/Xcode/DerivedData
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README is explicit that flutter clean and Xcode's Clean Build Folder are not enough on their own, because SwiftPM caches the evaluated Package.swift.&lt;/p&gt;

&lt;p&gt;If your repository does not put Info.plist where the plugin expects it, which is the normal situation in a monorepo, the README gives an environment variable instead: set &lt;code&gt;ARKIT_FACE_TRACKING_ENABLED=1&lt;/code&gt;, with &lt;code&gt;export&lt;/code&gt; for terminal builds and &lt;code&gt;launchctl setenv&lt;/code&gt; for builds started from the Xcode GUI.&lt;/p&gt;

&lt;p&gt;On older Flutter versions that still build with CocoaPods, the same switch is a post_install block in ios/Podfile that adds &lt;code&gt;-DENABLE_TRUEDEPTH_API&lt;/code&gt; to OTHER_SWIFT_FLAGS on the arkit_plugin target. If you have set up SPM, skip the Podfile step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is ARCore tracking choppy in my debug build?
&lt;/h3&gt;

&lt;p&gt;ar_flutter_plugin_plus warns about a bug in the ARCore library that interferes with tracking on some Android devices, and its workaround is to build with debuggable false in build.gradle. The README links the &lt;a href="https://github.com/google-ar/arcore-android-sdk/issues/1750" rel="noopener noreferrer"&gt;ARCore issue on GitHub&lt;/a&gt;. If tracking only looks rough in debug, rule that out before you blame the plugin. Its suggested Podfile also raises the iOS deployment target to 15.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  "can only be launched from Flutter tooling" on an iOS debug build
&lt;/h3&gt;

&lt;p&gt;augen's troubleshooting section covers this one: debug builds must be started by flutter run or Xcode, not from the home screen. If flutter run hangs waiting for the VM Service, it suggests closing Xcode and running flutter config --enable-lldb-debugging, or using flutter run --release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 5: what does the phone have to have?
&lt;/h2&gt;

&lt;p&gt;arkit_plugin notes that ARKit needs an A9 or later processor (iPhone 6s/7/SE/8/X, iPad 2017/Pro) on iOS 11 or newer, that some features need iOS 12, and that the plugin's own minimum is iOS 13.0. For faces, augen's iOS table maps face tracking to ARFaceTrackingConfiguration on the TrueDepth front camera.&lt;/p&gt;

&lt;p&gt;On Android, augen's troubleshooting list starts with two device checks that apply to every ARCore wrapper here: the device has to support ARCore, and Google Play Services for AR has to be installed.&lt;/p&gt;

&lt;p&gt;The ML Kit face mesh wants faces within about 2 meters of the camera, facing it, with at least half of the face visible. On iOS, ML Kit runs on 64-bit devices only.&lt;/p&gt;

&lt;p&gt;deepar_flutter sets its floor at Android SDK 23 and iOS 13.0.&lt;/p&gt;

&lt;p&gt;For arcore_flutter_plugin I read the versions list and the API reference only, so it has the thinnest entry in this post. That reflects my reading list and says nothing about the package's quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seven side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Tracks&lt;/th&gt;
&lt;th&gt;Publisher on pub.dev&lt;/th&gt;
&lt;th&gt;Version I read (upload date)&lt;/th&gt;
&lt;th&gt;Platforms that work&lt;/th&gt;
&lt;th&gt;Setup to plan for&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ar_flutter_plugin_plus&lt;/td&gt;
&lt;td&gt;Planes, anchors, images, cloud anchors&lt;/td&gt;
&lt;td&gt;xinix.tech (verified)&lt;/td&gt;
&lt;td&gt;1.1.3 (28 Jan 2026)&lt;/td&gt;
&lt;td&gt;Android, iOS&lt;/td&gt;
&lt;td&gt;debuggable false for the ARCore bug, iOS 15.0 in Podfile&lt;/td&gt;
&lt;td&gt;Open, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arkit_plugin&lt;/td&gt;
&lt;td&gt;World, face (opt-in), body&lt;/td&gt;
&lt;td&gt;leushchenko.com (verified)&lt;/td&gt;
&lt;td&gt;1.5.0 (3 Aug 2026)&lt;/td&gt;
&lt;td&gt;iOS&lt;/td&gt;
&lt;td&gt;TrueDepth flag plus SwiftPM cache clear&lt;/td&gt;
&lt;td&gt;Open, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arcore_flutter_plugin&lt;/td&gt;
&lt;td&gt;World, Augmented Faces&lt;/td&gt;
&lt;td&gt;gdifrancesco.dev (verified)&lt;/td&gt;
&lt;td&gt;0.1.0 (7 Feb 2023)&lt;/td&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;enableAugmentedFaces: true&lt;/td&gt;
&lt;td&gt;Open, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;augen&lt;/td&gt;
&lt;td&gt;World, images, face&lt;/td&gt;
&lt;td&gt;Unverified uploader&lt;/td&gt;
&lt;td&gt;1.5.0 (1 Oct 2026)&lt;/td&gt;
&lt;td&gt;Android, iOS; web for markers only&lt;/td&gt;
&lt;td&gt;ARCore required in manifest, USDZ on iOS, Dart 3.9.2+&lt;/td&gt;
&lt;td&gt;Open, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;google_mlkit_face_detection&lt;/td&gt;
&lt;td&gt;Face box, landmarks, contours&lt;/td&gt;
&lt;td&gt;flutter-ml.dev (verified)&lt;/td&gt;
&lt;td&gt;0.15.1 (17 Aug 2026)&lt;/td&gt;
&lt;td&gt;Android, iOS&lt;/td&gt;
&lt;td&gt;iOS 15.5, exclude armv7&lt;/td&gt;
&lt;td&gt;Open, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;google_mlkit_face_mesh_detection&lt;/td&gt;
&lt;td&gt;468-point face mesh&lt;/td&gt;
&lt;td&gt;flutter-ml.dev (verified)&lt;/td&gt;
&lt;td&gt;0.6.1 (17 Aug 2026)&lt;/td&gt;
&lt;td&gt;Android only, despite the iOS tag&lt;/td&gt;
&lt;td&gt;minSdkVersion 21&lt;/td&gt;
&lt;td&gt;Open, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepar_flutter&lt;/td&gt;
&lt;td&gt;Face, with vendor rendering&lt;/td&gt;
&lt;td&gt;deepar.ai (verified)&lt;/td&gt;
&lt;td&gt;0.0.5 (29 Jan 2023)&lt;/td&gt;
&lt;td&gt;Android, iOS&lt;/td&gt;
&lt;td&gt;Manual deepar.aar, ProGuard rules, permission_handler ^10&lt;/td&gt;
&lt;td&gt;Self-serve keys, paid by MAU above 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row in that table is stated in the text above, so nothing lives only in the table. The version column is what each pub.dev listing showed when I read it in October 2026, and every cell is a listing value I copied. Nothing in the table was measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the vendor SDKs that did not qualify?
&lt;/h2&gt;

&lt;p&gt;Three face SDKs are missing from the table, all for the same reason: you cannot get started on them without talking to someone. They fail differently, so they are worth separating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FaceUnity&lt;/strong&gt; has no pub.dev package that I found. Its own effects page, as it read in July 2026, lists Flutter among its platforms, and the documented Flutter route I found runs through Agora: &lt;a href="https://docs.agora.io/en/realtime-media/marketplace/build/add-video-and-ar-effects/faceunity/flutter" rel="noopener noreferrer"&gt;FaceUnity's AR Filter extension for the Agora RTC SDK&lt;/a&gt;. You add the native Android .aar files through Gradle and the iOS .framework files through CocoaPods, convert an authpack certificate to Dart, and enable it with enableExtension(provider: "FaceUnity", extension: "Effect", enable: true). The guide asks for Flutter 2.0.0 or higher and Dart 2.15.1 or higher. That route fits apps that already run Agora video calls, and it is a lot of plumbing for a camera filter outside a call. FaceUnity publishes no price list, and access starts with a trial application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visage Technologies&lt;/strong&gt; documents no Flutter or React Native wrapper on its pages as they read in June 2026, with Unity as the only engine it names, so the Flutter side would be platform channels you write yourself. Its pricing page at that time listed a free FaceTrack LITE license for one app up to 10,000 users and technical support billed separately at 1,500 euros per year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Banuba&lt;/strong&gt; is the opposite case, and the one worth knowing about if the January 2023 date on deepar_flutter is what rules DeepAR out for you. It does publish a Flutter package, &lt;a href="https://pub.dev/packages/banuba_sdk" rel="noopener noreferrer"&gt;banuba_sdk&lt;/a&gt;, under the verified publisher banuba.com, and it has shipped through 2026, with 3.1.4 uploaded on 10 July 2026 and 3.1.7 current when I read the listing in October 2026. Two things the listing does not give you: a license, which shows as unknown rather than MIT, and the platform tags, which were blank on the version page I opened. The access gate is what keeps it out of the screen: the 14-day demo token comes through a website form or a sales manager, not from a developer console, so you cannot get from pub.dev to a running app on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Flutter AR package fits which app?
&lt;/h2&gt;

&lt;p&gt;If you are building a face filter app on both platforms and want a vendor renderer, start with deepar_flutter, because it is the only self-serve package here built around switching prepared face effects (switchEffect) on a vendor renderer. Plan to own its Dart layer, since nothing has shipped since January 2023, and check the permission_handler pin against your app first.&lt;/p&gt;

&lt;p&gt;If you are building your own overlay or analytics on face geometry, use google_mlkit_face_detection, because it covers Android and iOS from a verified publisher and shipped in August 2026. Add google_mlkit_face_mesh_detection only for an Android-only mesh feature.&lt;/p&gt;

&lt;p&gt;If your face feature is iOS only, use arkit_plugin, because it shipped in August 2026 and exposes ARKit face tracking once you turn on the TrueDepth flag.&lt;/p&gt;

&lt;p&gt;If you need world AR on both platforms (plane anchors, placed models, cloud anchors), use ar_flutter_plugin_plus over the original ar_flutter_plugin, because the fork shipped in 2026 and the original has not had a release since November 2022.&lt;/p&gt;

&lt;p&gt;If you want one Dart API over ARCore and RealityKit with faces included, try augen, the newest upload in the set, and weigh that against an unverified uploader and a Dart 3.9.2 minimum.&lt;/p&gt;

&lt;p&gt;If you need face effects in a Flutter web build, none of these seven covers it, so plan a separate web path from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Face tracking in Flutter still means a mobile build and native setup
&lt;/h2&gt;

&lt;p&gt;Across the seven, the Dart API was the easy part. Every face option is mobile only, every one asks for native configuration that flutter pub get does not do, and the upload dates run from October 2026 back to January 2023.&lt;/p&gt;

&lt;p&gt;Two findings surprised me: the face mesh package's iOS tag, and the permission_handler pin in DeepAR's own package. Both are visible on the listing pages before anyone writes a line of code, which is the argument for running this screen at all. The device round takes the survivors onto hardware next.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>ios</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Virtual Try On Glasses: 7 Checks Before You Trust a Demo</title>
      <dc:creator>Dave Gordon</dc:creator>
      <pubDate>Wed, 14 Oct 2026 13:15:28 +0000</pubDate>
      <link>https://dev.to/dave_gordon/virtual-try-on-glasses-7-checks-before-you-trust-a-demo-1hj6</link>
      <guid>https://dev.to/dave_gordon/virtual-try-on-glasses-7-checks-before-you-trust-a-demo-1hj6</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;br&gt;
A virtual try on glasses demo is a best-case recording. I run seven checks on a live camera before I believe one: real-world scale, temple occlusion, head turns, lens rendering, jitter, bad light, and how a frame gets into the catalog. This post is a checklist. I have not published measurements here.&lt;/p&gt;

&lt;p&gt;I look at a lot of virtual try on glasses demos for client projects. Most of them look fine in the first ten seconds, because the demo is a frontal face, good light and one hand-tuned frame. Shoppers do not behave like that. They tilt the phone, sit by a window and try thirty frames in a row.&lt;/p&gt;

&lt;p&gt;So I test the way a shopper breaks things. Each check below is something you can run yourself in under a minute, on the device you actually care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I actually measure for this post?
&lt;/h2&gt;

&lt;p&gt;No numbers, and I want that stated before the checks. This is step zero: the method. If I publish measurements later they will be per device, never averaged, with the phone model, OS version and chipset written next to every number. For now, if you run the checks yourself, you will know what to ask any vendor to show you.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you test virtual try on glasses on a real face?
&lt;/h2&gt;

&lt;p&gt;Run the seven checks in this order. The early ones fail fastest.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Is the frame the right size, in millimeters?
&lt;/h3&gt;

&lt;p&gt;A try-on that looks good but renders every frame at one default width is a styling toy.&lt;/p&gt;

&lt;p&gt;Take a physical frame and read the numbers printed on the inner arm, for example 52-18-140. Those are lens width, bridge width and temple length, in millimeters. Note what they are not: 140 is the length of the arm that runs to your ear, not how wide the frame is. The front of that frame measures roughly 2 x 52 + 18 = 122 mm across, plus a millimeter or two of hinge. If a vendor quotes you the third number as a width, that alone tells you something.&lt;/p&gt;

&lt;p&gt;Here is the arithmetic you can redo. Say the overlay renders that 122 mm front 2 mm too narrow. That is 2 / 122, about 1.6%. Small on its own, and you probably will not see it.&lt;/p&gt;

&lt;p&gt;Now add the pupillary distance. Adult PD runs roughly 54 to 74 mm, with 63 mm a common middle. An estimate that is 3 mm off on a 63 mm PD is a 4.8% error, and it puts each lens center about 1.5 mm away from the eye it is supposed to sit in front of. Combine a 1.6% scale error with a 4.8% PD error and the frame stops sitting where the eyes say it should, even though neither number sounds alarming on its own. Illustrative numbers, so swap in yours.&lt;/p&gt;

&lt;p&gt;What I look for: does the vendor use a stated scale reference, and does the same frame in two sizes render at two sizes?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Do the temples disappear behind the ears?
&lt;/h3&gt;

&lt;p&gt;Turn your head slowly to the left. On a real face the far temple arm goes behind the ear and the near one stays visible. If the arms float in front of the cheek, the occlusion is missing. This is the single most common tell I see in cheap overlays.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. What happens at 45 degrees and beyond?
&lt;/h3&gt;

&lt;p&gt;Sweep from frontal to a hard yaw, then pitch up and down. Tracking that holds at 10 degrees and snaps at 40 loses the shopper exactly when they check the side profile. Landmark models tend to be happiest on near-frontal faces, which is why the sweep is worth doing. For reference, Google's &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker" rel="noopener noreferrer"&gt;MediaPipe Face Landmarker&lt;/a&gt; documents 478 landmarks per face, and even a model with that many points can lose them when half the face leaves the frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Do the lenses look like lenses?
&lt;/h3&gt;

&lt;p&gt;Tinted, mirrored and clear lenses need different treatment. Clear lenses should not turn the eyes into a gray rectangle. Look for glare that moves when you turn, and for frame edges that stay crisp against hair and skin. If every material looks like matte plastic, the asset pipeline is flat.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Is the frame steady when you hold still?
&lt;/h3&gt;

&lt;p&gt;Hold the phone against a stack of books and watch for the frame swimming a pixel or two on a face that is not moving. Jitter is cheap to hide in a recording and hard to hide live. I count it as a fail when I notice it without looking for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Does it survive a window behind you, and your own glasses?
&lt;/h3&gt;

&lt;p&gt;Backlight, a dim room and a face already wearing glasses are three separate tests. Many tracking stacks quietly assume bare faces. If the shopper's own pair confuses the overlay, the first thing they will do is take them off, and many will not.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. How does a new frame get into the catalog?
&lt;/h3&gt;

&lt;p&gt;This is where projects go over budget, and it is the check almost nobody runs because it is not visual.&lt;/p&gt;

&lt;p&gt;Ask four things, in this order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What does the merchant supply per SKU?&lt;/strong&gt; Product photographs, a studio capture session, or a 3D model someone has to build. These are three different budgets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many images per frame, and from which angles?&lt;/strong&gt; The answer separates a flow you can run on the photos already in your product feed from one that needs a turntable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do colorways need separate capture?&lt;/strong&gt; A catalog of 400 frames in three colors each is 1,200 SKUs or 400, depending on the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How long does a collection take to go live, and who did the work in the number you were quoted?&lt;/strong&gt; "Two days" means something different when the vendor's team did it.
A photo-based flow and a 3D-modeling flow have very different costs, and a try-on that works for twenty hero frames and breaks at two thousand SKUs only works as a lookbook.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do I score what I see?
&lt;/h2&gt;

&lt;p&gt;I use a plain pass / partial / fail per check, written next to the device it ran on. The table is my own scoring sheet. The columns and the thresholds are mine, not an industry standard.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What I look at&lt;/th&gt;
&lt;th&gt;Fail looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Scale&lt;/td&gt;
&lt;td&gt;Printed frame size vs on-screen size&lt;/td&gt;
&lt;td&gt;Every frame renders the same width&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Temples&lt;/td&gt;
&lt;td&gt;Far arm during a slow head turn&lt;/td&gt;
&lt;td&gt;Arms float in front of the cheek&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Head turns&lt;/td&gt;
&lt;td&gt;Yaw and pitch sweep&lt;/td&gt;
&lt;td&gt;Overlay snaps or detaches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Lenses&lt;/td&gt;
&lt;td&gt;Clear, tinted, mirrored&lt;/td&gt;
&lt;td&gt;Everything looks matte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Jitter&lt;/td&gt;
&lt;td&gt;Phone still against a stack of books&lt;/td&gt;
&lt;td&gt;Visible swim on a still face&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. Light and own glasses&lt;/td&gt;
&lt;td&gt;Backlight, dim room, glasses on&lt;/td&gt;
&lt;td&gt;Frame lost or misplaced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. Onboarding&lt;/td&gt;
&lt;td&gt;Images and time per new SKU&lt;/td&gt;
&lt;td&gt;Only hero frames are available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row in that table is stated in the text above, so nothing lives only in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should I test on my own phone or the vendor's?
&lt;/h2&gt;

&lt;p&gt;Yours, plus the oldest mid-range phone in your test pool. A flagship is the best case for every one of these checks, and that is the device the demo was recorded on. I wrote up the same habit for face AR SDKs in &lt;a href="https://medium.com/@davegord86/what-i-learned-evaluating-face-ar-sdks-for-production-apps-dd4ddb939319" rel="noopener noreferrer"&gt;8 Face AR SDK Checks I Run Before Signing a Contract&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does color matter for glasses at all?
&lt;/h2&gt;

&lt;p&gt;It matters less than for makeup, but it still shows up in tinted lenses and acetate frames under warm light. The long version of why rendering never matches the real thing is in &lt;a href="https://medium.com/@davegord86/virtual-makeup-shade-accuracy-4767e893cf67" rel="noopener noreferrer"&gt;6 reasons virtual makeup rendering never matches the real shade&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not cover
&lt;/h2&gt;

&lt;p&gt;This checklist says nothing about purchase conversion, return rates or pricing. It also says nothing about which product to buy, because I have not run these checks against named products for this post. Treat any vendor claim about fit accuracy as a claim until you have run checks 1 to 3 yourself on a few real faces.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you only run three
&lt;/h2&gt;

&lt;p&gt;Run 2, 3 and 7. Temple occlusion and a hard head turn expose weak tracking in under a minute, and the onboarding question exposes weak economics before you sign anything. Everything else is polish you can negotiate.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>frontend</category>
      <category>mobile</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Where Background Removal APIs Send Your Users' Video</title>
      <dc:creator>Dave Gordon</dc:creator>
      <pubDate>Mon, 14 Sep 2026 19:13:00 +0000</pubDate>
      <link>https://dev.to/dave_gordon/where-background-removal-apis-send-your-users-video-2bl2</link>
      <guid>https://dev.to/dave_gordon/where-background-removal-apis-send-your-users-video-2bl2</guid>
      <description>&lt;p&gt;Picking a background removal API is an engineering decision right up until it stops being one. The masks hold on hair and glasses, the latency fits, the integration is a two day job, and then a security reviewer at your own company or at your largest customer sends over a questionnaire asking where the frames go and who keeps them afterwards. The API reference does not cover any of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Remove.bg puts no duration on how long the images sent to it are kept, and it caps server logs at three months.&lt;/li&gt;
&lt;li&gt;Photoroom's model training clause covers photos people send to its app, and the same text excludes API images.&lt;/li&gt;
&lt;li&gt;VideoBGRemover publishes a full erasure sequence: live while the job is active, gone on a verified request, production erasure inside 30 days, backups seven days beyond.&lt;/li&gt;
&lt;li&gt;Files on the fal CDN are readable by anyone holding the link unless you attach an access control list, and inputs inherit the account default rather than the header on the inference call.&lt;/li&gt;
&lt;li&gt;Banuba's Face AR SDK is licensed by a token in .txt format with a published expiry ladder, and the same page never says whether the SDK phones home to check it.&lt;/li&gt;
&lt;li&gt;One of the five is sales gated. The other four hand you a key at sign-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Everything here was read off vendor pages on 8 September 2026
&lt;/h2&gt;

&lt;p&gt;I ran no tests for this one and there is not a millisecond anywhere below. Every claim comes from a vendor's own live pages as they stood on 8 September 2026, linked next to the sentence it supports. Those pages get edited, often quietly, and any figure that decides a build should come from the vendor in writing, addressed to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the video frame leave the device?
&lt;/h2&gt;

&lt;p&gt;Four of the five are hosted services, so the media crosses the network before anything happens to it. &lt;a href="https://www.remove.bg/privacy" rel="noopener noreferrer"&gt;Remove.bg's privacy policy&lt;/a&gt;, last updated 16 July 2025, set out the sequence plainly when I read it on 8 September 2026: images go up securely, get processed, and are provided for download. Photoroom, VideoBGRemover and Veed running on fal.ai work the same way.&lt;/p&gt;

&lt;p&gt;Banuba's Face AR SDK is the one product here that is not a hosted service, since the processing happens inside your app rather than on a server you call. That settles the media path and leaves the license path open, which is where its published material stops short.&lt;br&gt;
For the hosted four, the honest questionnaire entry is yes. Everything else is argued after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does each vendor keep, and for how long?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Banuba Face AR SDK&lt;/th&gt;
&lt;th&gt;Remove.bg&lt;/th&gt;
&lt;th&gt;Photoroom&lt;/th&gt;
&lt;th&gt;VideoBGRemover&lt;/th&gt;
&lt;th&gt;Veed on fal.ai&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where processing happens&lt;/td&gt;
&lt;td&gt;On the device, inside your app&lt;/td&gt;
&lt;td&gt;Vendor servers&lt;/td&gt;
&lt;td&gt;Vendor servers&lt;/td&gt;
&lt;td&gt;Vendor servers&lt;/td&gt;
&lt;td&gt;fal's platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does user media leave the device&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is being compared&lt;/td&gt;
&lt;td&gt;On-device SDK, licensed by a token in the client app&lt;/td&gt;
&lt;td&gt;Hosted REST image service&lt;/td&gt;
&lt;td&gt;Hosted REST service plus a consumer app&lt;/td&gt;
&lt;td&gt;Hosted video service&lt;/td&gt;
&lt;td&gt;A model run on fal's platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access&lt;/td&gt;
&lt;td&gt;Sales gated: demo and commercial tokens are both issued by a person&lt;/td&gt;
&lt;td&gt;Open self-serve with an API key&lt;/td&gt;
&lt;td&gt;Open self-serve, custom enterprise pricing&lt;/td&gt;
&lt;td&gt;Open self-serve, enterprise DPA available&lt;/td&gt;
&lt;td&gt;Open self-serve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How long the vendor holds media&lt;/td&gt;
&lt;td&gt;No media reaches a vendor server&lt;/td&gt;
&lt;td&gt;"Delete them shortly after", no duration given&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Live while the account or job is active, erased on job deletion or a verified request&lt;/td&gt;
&lt;td&gt;No CDN expiry stated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tail after deletion, and logs&lt;/td&gt;
&lt;td&gt;No media reaches a vendor server. Server logs not applicable&lt;/td&gt;
&lt;td&gt;Server logs kept at most three months&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Production erasure inside 30 days, soft-delete systems and rolling backups seven days beyond&lt;/td&gt;
&lt;td&gt;Inputs and outputs kept 30 days by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training use&lt;/td&gt;
&lt;td&gt;No media reaches a vendor server&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Training row covers app photos, API images carved out&lt;/td&gt;
&lt;td&gt;Does not train or improve models on customer content&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Named third-party audit&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;td&gt;SOC 2 Type 2&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract instrument named&lt;/td&gt;
&lt;td&gt;Not named, terms go through an account manager&lt;/td&gt;
&lt;td&gt;EU and UK representative named (EDPO), rights listed&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;td&gt;DPA incorporating the 2021 Standard Contractual Clauses&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control you can set yourself&lt;/td&gt;
&lt;td&gt;Keep the token on your own server rather than in the app bundle&lt;/td&gt;
&lt;td&gt;Not offered&lt;/td&gt;
&lt;td&gt;Not offered&lt;/td&gt;
&lt;td&gt;Job deletion and verified erasure requests&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;X-Fal-Store-IO: 0&lt;/code&gt; on the request, ACL on the upload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still open on their own pages&lt;/td&gt;
&lt;td&gt;Whether the SDK makes a network call to validate the token&lt;/td&gt;
&lt;td&gt;How long processed images are actually kept&lt;/td&gt;
&lt;td&gt;Retention duration for API media&lt;/td&gt;
&lt;td&gt;No named outside audit&lt;/td&gt;
&lt;td&gt;CDN file expiry, and what happens to assets at account end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every cell above is what that vendor's own published pages said when I checked them on 8 September 2026, and an empty-looking cell means I found nothing on the subject, not that the vendor does the opposite. None of it is a measurement.&lt;/p&gt;

&lt;p&gt;The first row exists so the first column does not get scanned as like-for-like. An on-device SDK and a hosted video service fail a questionnaire in completely different places, and putting them side by side is only useful if you keep that in view.&lt;/p&gt;

&lt;p&gt;Access splits four to one. Remove.bg, Photoroom, VideoBGRemover and fal all issue a working key at sign-up. Banuba is the gated one: a demo credential comes from a sales manager or the website request form, and a commercial credential comes from an account manager after payment. Sales gating is not automatically a problem, but it does mean your evaluation timeline includes somebody else's calendar.&lt;/p&gt;

&lt;p&gt;The most specific writing about retention here belongs to VideoBGRemover. Its &lt;a href="https://videobgremover.com/privacy" rel="noopener noreferrer"&gt;privacy page&lt;/a&gt; says uploaded videos, masks, previews and outputs stay available while the account or job is active and are erased when you delete the job or send a verified request, that production erasure completes within 30 days, and that soft-delete systems and rolling backups may hold copies for seven days beyond that. No other page here puts a tail on the number. Remove.bg's three month ceiling on server logs and the 30 day payload default in &lt;a href="https://fal.ai/docs/documentation/model-apis/media-expiration" rel="noopener noreferrer"&gt;fal's media expiration docs&lt;/a&gt; are real figures too, though both of those describe logs and JSON rather than the media itself.&lt;/p&gt;

&lt;p&gt;On model training, Photoroom and VideoBGRemover arrive at similar comfort from opposite directions. Photoroom carves API traffic out of model improvement in its own legal text. VideoBGRemover rules out training on customer content across the whole service.&lt;/p&gt;

&lt;p&gt;Photoroom is the only one of the five naming an outside auditor: &lt;a href="https://www.photoroom.com/api" rel="noopener noreferrer"&gt;its API page&lt;/a&gt; states SOC 2 Type 2. If your reviewer's checklist has a certification line on it, that is currently a one horse race here.&lt;/p&gt;

&lt;p&gt;For a contract instrument, VideoBGRemover names an enterprise DPA incorporating the European Commission's 2021 Standard Contractual Clauses, and Remove.bg names EDPO as its EU and UK representative alongside the usual list of data-subject rights. The other three leave that conversation to sales.&lt;/p&gt;

&lt;p&gt;fal is alone in handing you controls you operate yourself. Everyone else describes their own conduct and asks you to accept the description, which is a weaker artifact to walk into a review with, however honest the description turns out to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can anyone else open the file the API handed back?
&lt;/h2&gt;

&lt;p&gt;On fal, the default is yes. fal's &lt;a href="https://fal.ai/docs/documentation/model-apis/file-access-controls" rel="noopener noreferrer"&gt;file access control docs&lt;/a&gt; state that every object on the fal CDN carries an access control list, and that by default CDN URLs are reachable by anyone who has the link. A long random URL does not make an object private, and links leak through logs, support tickets and screenshots.&lt;/p&gt;

&lt;p&gt;Inputs are a separate case on the same page. The SDKs push them to the CDN before the inference call runs, so they are not governed by the ACL header you set on that call and instead inherit the account default. To constrain an input you set the ACL on the upload itself.&lt;/p&gt;

&lt;p&gt;To size your own exposure, with named inputs you can redo on paper:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with clips per user per day. Mine here is one, and it is yours to substitute.&lt;/li&gt;
&lt;li&gt;Multiply by daily active users who touch the feature. I am using 500, an illustrative figure to replace with your own.&lt;/li&gt;
&lt;li&gt;500 x 1 = 500 input objects landing on the CDN each day.&lt;/li&gt;
&lt;li&gt;CDN URLs are public by default, so unless an ACL is attached at upload time, those are 500 publicly reachable URLs added per day.&lt;/li&gt;
&lt;li&gt;The JSON payloads default to 30 days, so 500 x 30 = 15,000 request records held in the history at any steady state moment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In elapsed terms, that history never empties while the feature is switched on, because the oldest record ages out 30 days after it was created and a fresh one arrives the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are my users' images used to train the vendor's models?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.photoroom.com/legal/privacy" rel="noopener noreferrer"&gt;Photoroom's privacy policy&lt;/a&gt; carries a retention table with a row titled "Training our AI model", covering photos and images that people sent to Photoroom, on a legitimate interest basis, held for as long as training requires. The same document then states that this model improvement does not apply to images processed through the API. That clause is about app users, not API traffic, and getting the distinction backwards in a questionnaire will cost you a week of email.&lt;/p&gt;

&lt;p&gt;VideoBGRemover takes the other route and states flatly that it does not use content people send it, or content it generates, to train or improve machine learning models. That single sentence covers the whole service.&lt;/p&gt;

&lt;p&gt;Remove.bg's policy, fal's legal pages and the Banuba licensing page say nothing either way, going by what was up on 8 September 2026. That absence does not amount to a denial, and it stays open until the vendor answers in email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who else on my team can see the requests?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fal.ai/legal/privacy-policy" rel="noopener noreferrer"&gt;fal's privacy policy&lt;/a&gt; is unusually direct about this. If you are a member of a Team Account, it says, other members of that Team Account may view billing information, API keys, and AI model requests, including any input and output data.&lt;/p&gt;

&lt;p&gt;Think that through before you invite the contractor. Whoever sits in the account can read the media your users sent, and your key too.&lt;/p&gt;

&lt;p&gt;The other four say nothing about who inside a customer account can read request bodies, in anything they published as of 8 September 2026. If the question comes up, and it always has for me, you will be relaying an email rather than pointing at a URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens to the feature when the license lapses?
&lt;/h2&gt;

&lt;p&gt;Banuba is the only one of the five that publishes what happens after the license runs out. Its &lt;a href="https://docs.banuba.com/far-sdk/tutorials/capabilities/token_management" rel="noopener noreferrer"&gt;token management documentation&lt;/a&gt; describes the token as an automatically generated set of characters in .txt format, unique to each client, that activates the licensed SDK functionality in the client app.&lt;/p&gt;

&lt;p&gt;The expiry ladder on that page has three rungs. A credential that has not expired means the SDK works as expected. One expired by a month or less still works, with a watermark displayed. Past a month, the functionality will not work.&lt;/p&gt;

&lt;p&gt;The same page recommends keeping the credential on your own server instead of in the app bundle, and the reasoning is operational: ship it inside the binary and every renewal means a new build queued for App Store or Play Store approval.&lt;/p&gt;

&lt;p&gt;What the page does not state is whether the SDK contacts Banuba at runtime to validate the credential. The expiry ladder is written down. Whether a network call sits behind it is left open, so a reviewer asking about outbound traffic needs that confirmed by the vendor.&lt;/p&gt;

&lt;p&gt;Among the hosted four, only VideoBGRemover ties asset availability to account state in its published terms, saying that videos, masks, previews and outputs stay available while the account or job is active. For Remove.bg, Photoroom and fal, I found nothing published about what becomes of stored assets once an account ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What will the vendor put in writing?
&lt;/h2&gt;

&lt;p&gt;Photoroom offers an attestation from an outside auditor plus an explicit carve-out for API traffic. VideoBGRemover offers a DPA built on the 2021 Standard Contractual Clauses for enterprise customers under GDPR. Remove.bg names a representative for EU and UK data subjects and enumerates the rights those people hold. fal offers something different in kind, two controls you operate yourself: the store header on the request and the ACL on the upload. Banuba issues its commercial license through an account manager after payment, so the terms conversation happens with a person and whatever you need has to end up in that thread.&lt;/p&gt;

&lt;p&gt;Two of the five publish noticeably less than the rest. Remove.bg gives no duration whatsoever for the images sent to it, only that they go "shortly after", and shortly is not a number your reviewer can log. Banuba's material covers the license credential thoroughly and stops before the SDK's runtime network behavior, which leaves the most basic question about an on-device product unresolved on the public web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for the four answers in writing before you sign
&lt;/h2&gt;

&lt;p&gt;Four items close most of the gaps above, and they fit in one email.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A number, in days, for how long processed media is held, and a second number for the backup tail after removal.&lt;/li&gt;
&lt;li&gt;Whether traffic through the API is excluded from model training, stated separately from whatever applies to their consumer app.&lt;/li&gt;
&lt;li&gt;A named list of who inside your own account can read request inputs and outputs.&lt;/li&gt;
&lt;li&gt;What becomes of stored assets when the contract or license ends.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of the five publishes some of those, and none of the five had all four on the pages I went through on 8 September 2026. Whichever come back thin are the ones to raise before the integration lands in a sprint, since they get harder to ask once the feature ships.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>api</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 Places a Real-Time Camera Filter Drops Frames</title>
      <dc:creator>Dave Gordon</dc:creator>
      <pubDate>Thu, 03 Sep 2026 11:42:31 +0000</pubDate>
      <link>https://dev.to/dave_gordon/5-places-a-real-time-camera-filter-drops-frames-27lg</link>
      <guid>https://dev.to/dave_gordon/5-places-a-real-time-camera-filter-drops-frames-27lg</guid>
      <description>&lt;p&gt;A real-time camera filter has about 33 milliseconds per frame at 30 FPS. When a filter that looked fine in a demo stutters on a real handset, the loss is almost always in one of five places: format conversion, frame backpressure, where inference actually executes, synchronous readback, or sustained thermal load. Here is how I find which one.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The budget is arithmetic you can do in your head: 1000 / 30 = 33.3 ms per frame, and 1000 / 60 = 16.7 ms.&lt;/li&gt;
&lt;li&gt;Converting a camera frame to RGBA before anything else executes is the most common self-inflicted cost, and a byte count exposes it before you open a profiler.&lt;/li&gt;
&lt;li&gt;If you never chose a backpressure strategy you already have the non-blocking one, which puts a leaked frame reference ahead of the strategy on the list of suspects.&lt;/li&gt;
&lt;li&gt;A model that reports a plausible inference time can still be executing somewhere you did not pick, which makes the number describe a different pipeline from the one you are shipping.&lt;/li&gt;
&lt;li&gt;Reading pixels back synchronously drains the queue you spent the whole frame filling.&lt;/li&gt;
&lt;li&gt;Thermal decay does not show up in a 30 second run, which is why most of these pipelines pass their own tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  No device numbers in this piece
&lt;/h2&gt;

&lt;p&gt;I have not benchmarked anyone's SDK here and there are no handset figures below. A number without a named device, an OS build and a thermal state cannot be reused by anyone reading it. What follows is the order I work through when a camera pipeline misses its budget, plus the arithmetic that tells you whether a given stage can possibly fit before you go looking for it in a trace.&lt;/p&gt;

&lt;p&gt;What I did do is verify every mechanism against primary documentation and link it: the image analysis contract and frame format from Android's own docs, the delegate behaviour from the TensorFlow Lite repository and its issue tracker, the profiling tools from Google and Apple, and the readback stall from a handset vendor's own optimisation guidance. The arithmetic is reproducible on paper, two divisions and a multiplication, so if any of it is wrong you can check it in about a minute.&lt;/p&gt;

&lt;p&gt;The five places below are ordered by how often they turn out to be the answer in my own work. Ranking them by theoretical cost would give a different order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The budget, before you profile anything
&lt;/h2&gt;

&lt;p&gt;Two divisions set the ceiling for everything else.&lt;/p&gt;

&lt;p&gt;At 30 FPS you have 1000 / 30 = 33.3 ms to receive a frame, convert it, run whatever model you are running, render, and composite. At 60 FPS that halves to 1000 / 60 = 16.7 ms. Each stage you add spends part of that, and the preview is only as smooth as its slowest frame.&lt;/p&gt;

&lt;p&gt;Resolution is the other multiplier, and it is worth doing on paper first. A 1080p frame is 1920 x 1080 = 2,073,600 pixels. A 720p frame is 1280 x 720 = 921,600 pixels. That is a factor of 2.25, so any per-pixel stage costs 2.25 times more at 1080p than at 720p before you change a line of code.&lt;/p&gt;

&lt;p&gt;I start here because the arithmetic frequently ends the investigation on its own. If a stage has to touch every pixel of a 1080p frame and you have four such stages, the budget was gone before the model loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I need 60 FPS, or is 30 enough?
&lt;/h2&gt;

&lt;p&gt;For a camera filter that a person is looking at while moving their own face, 30 is usually enough and 60 is usually not worth what it costs, because halving the budget to 16.7 ms tends to force a resolution cut that is more visible than the smoothness gain.&lt;/p&gt;

&lt;p&gt;The exception is anything the user physically tracks with their hand or head, where the extra samples do register. I would decide this by shipping 30 and instrumenting it before assuming the answer, since the 95th percentile at 30 FPS is a better predictor of perceived quality than the target rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The format conversion you did not know you were paying for
&lt;/h2&gt;

&lt;p&gt;Camera hardware on Android hands you &lt;code&gt;YUV_420_888&lt;/code&gt;, a planar YCbCr format with 8 bits per sample and chroma subsampled 4:2:0. Most image processing code, and most tutorial code, wants RGBA instead. So a conversion gets inserted, often before anyone has decided whether it is needed.&lt;/p&gt;

&lt;p&gt;The cost is a byte count you can compute without a profiler.&lt;/p&gt;

&lt;p&gt;At 4:2:0, a pixel averages 1.5 bytes. A 1080p YUV frame is 2,073,600 x 1.5 = 3,110,400 bytes, roughly 3.11 MB. The same frame as &lt;code&gt;RGBA_8888&lt;/code&gt; at 4 bytes per pixel is 2,073,600 x 4 = 8,294,400 bytes, roughly 8.29 MB. Running that conversion at 30 FPS means writing 8.29 x 30 = 248.7 MB per second, versus 93.3 MB/s if you had left the frame in its native layout.&lt;/p&gt;

&lt;p&gt;That traffic is not free on a mid-range memory bus, and it happens before your actual effect has done anything.&lt;/p&gt;

&lt;p&gt;What I look at: whether the conversion is needed at all. A shader can sample the Y and UV planes directly and do the color transform on the GPU as part of a pass you are already running. CameraX will also hand &lt;code&gt;ImageAnalysis&lt;/code&gt; an RGBA output natively if you genuinely need RGBA, which at least moves the conversion out of your own loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why does my camera preview freeze after a few seconds on Android?
&lt;/h2&gt;

&lt;p&gt;The cause is usually more boring than a slow filter.&lt;/p&gt;

&lt;p&gt;If you are using CameraX &lt;code&gt;ImageAnalysis&lt;/code&gt;, the analyzer contract puts the responsibility on you to close each image. Google's documentation is explicit that if images are not closed, they may block further images from being produced, which stalls the preview, or get dropped according to whichever backpressure strategy is configured. Earlier versions closed the image for you; current ones do not, so that multi-frame analysis stays possible. Close the &lt;code&gt;ImageProxy&lt;/code&gt;, not the &lt;code&gt;Media.Image&lt;/code&gt; it wraps, because closing the wrapped image directly breaks the image sharing mechanism inside CameraX.&lt;/p&gt;

&lt;p&gt;So a freeze that arrives a second or two in, instead of immediately, usually means a leaked frame reference. Filter speed is a red herring in that case.&lt;/p&gt;

&lt;p&gt;The second half of this is the strategy itself, and it is worth knowing what you already have. Non-blocking is the default: &lt;code&gt;STRATEGY_KEEP_ONLY_LATEST&lt;/code&gt; caches only the newest frame in a buffer one deep and overwrites it while you are still working on the previous one. That is almost always what a live filter wants, because a queued frame is a stale frame by the time it renders. So if you never set a strategy, you are already on the non-blocking one, which means a freeze is far more likely to be a frame you did not close than a strategy you did not pick.&lt;/p&gt;

&lt;p&gt;The blocking variety has to be chosen deliberately. &lt;code&gt;STRATEGY_BLOCK_PRODUCER&lt;/code&gt; queues images and starts dropping only once the queue is full, and Android's documentation notes that the blocking occurs across the entire camera device scope: if several use cases are bound to that camera, all of them stall while CameraX works through the queue. That is the mechanism that turns a slow analyzer into a frozen preview instead of a dropped frame or two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// androidx.camera:camera-core:1.3.4&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;analysis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ImageAnalysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBackpressureStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ImageAnalysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STRATEGY_KEEP_ONLY_LATEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ImageProxy&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;frameClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;// see the timing helper below&lt;/span&gt;
        &lt;span class="nf"&gt;renderEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// your pipeline&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;// ImageProxy.close(), required including on the failure path&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// When the screen goes away, including when the user backgrounds the app&lt;/span&gt;
&lt;span class="c1"&gt;// mid-session rather than pressing a stop button:&lt;/span&gt;
&lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearAnalyzer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cameraProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unbind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;finally&lt;/code&gt; block matters more than it looks. One exception on one frame, with &lt;code&gt;close()&lt;/code&gt; sitting on the happy path only, produces the symptom above and nothing in the log that points at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The model may not be running where you think
&lt;/h2&gt;

&lt;p&gt;A GPU delegate that reports a believable inference time is not proof that inference ran on the GPU. TensorFlow Lite's GPU delegate documentation covers which operations the backend supports, and unsupported ops may fall back to CPU depending on version and configuration. Reported behavior is not uniform: developers have filed cases where the expected fallback did not happen and produced a runtime error instead, and cases where it silently did.&lt;/p&gt;

&lt;p&gt;Either way, a plausible millisecond figure comes back. It may be describing an execution path other than the one you intend to ship.&lt;/p&gt;

&lt;p&gt;That picture comes from the documentation and the issue tracker, so verify it against the version you actually build with before you rely on it.&lt;/p&gt;

&lt;p&gt;What I look at: the execution path in a trace, instead of trusting the timing. On Android that means Perfetto, which has shipped in Android system images since Android 9 and has been enabled by default on most devices since Android 11. On iOS the Metal debugger and Metal System Trace in Xcode show the parallel CPU and GPU timeline, which is where a stage that claimed the GPU but ran elsewhere becomes obvious.&lt;/p&gt;

&lt;p&gt;I have not tried to name the exact log line for a fallback here. The strings vary across versions and I could not verify a current one while writing this, so I have left it out. The symptom is the reliable signal: GPU utilization that stays flat while inference time stays constant.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why does my frame rate drop when I read pixels back from the GPU?
&lt;/h2&gt;

&lt;p&gt;If any stage pulls pixels back from the GPU with a blocking read, the pipeline empties.&lt;/p&gt;

&lt;p&gt;The mechanism is well understood and worth stating precisely, because it explains why the cost never shows up where you look for it. A blocking readback cannot return until every queued draw command ahead of it has completed, so the CPU waits for the GPU to drain. The call itself is cheap. The stall is what costs you, and it lands on whichever line touches the data.&lt;/p&gt;

&lt;p&gt;Samsung's own OpenGL ES guidance, which is a handset maker writing about its own hardware, is one of the clearer public writeups of the pattern. The standard remedy is a pixel buffer object: bind the buffer, issue the read, and it returns immediately while the copy happens asynchronously, then use a fence to check for completion a frame or two later.&lt;/p&gt;

&lt;p&gt;The question I ask first: is the readback needed on every frame at all? Histogram, autoexposure and face-region logic often tolerate a fraction of the preview rate, and dropping one of those to every fifth frame is usually a larger win than optimizing the read itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The run was too short to fail
&lt;/h2&gt;

&lt;p&gt;A pipeline that holds its budget for 30 seconds tells you very little, because a phone under sustained camera load heats up and the governor responds by lowering clocks. The camera sensor, the GPU and the neural accelerator all work at once, which is close to a worst case for sustained power draw.&lt;/p&gt;

&lt;p&gt;What I look at: I run one scene for several minutes, log per-frame intervals throughout, and compare the last minute with the first. If the tail is materially worse than the head, the problem is thermal and no amount of per-stage optimization will move it. Screen brightness, whether the device is charging, and case material all change the result, which is why this one has to run on real hardware in a realistic state, never on a bench.&lt;/p&gt;

&lt;p&gt;It is also the step teams skip most often, since it is slow and it needs a person holding a phone.&lt;/p&gt;

&lt;p&gt;Here is the timing helper referenced in the snippet above. It keeps the full interval history, since averaging smooths away the exact spikes you are hunting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FrameClock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;intervalsMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ArrayDeque&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;lastNs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowNs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nanoTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastNs&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervalsMs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;intervalsMs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeFirst&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;intervalsMs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLast&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nowNs&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastNs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;lastNs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowNs&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/** p is 0.0 to 1.0. Returns null until at least one interval is recorded. */&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;percentileMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervalsMs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sorted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;intervalsMs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;percentileMs(0.95)&lt;/code&gt;. A pipeline averaging 28 ms with a 95th percentile of 70 ms is visibly janky, and the average on its own will never say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The budget sheet I actually fill in
&lt;/h2&gt;

&lt;p&gt;I keep this per device, per resolution. It is the artifact I hand to whoever asks why the filter is slow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The budget sheet I actually fill in
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;The budget I start from&lt;/th&gt;
&lt;th&gt;How I confirm it&lt;/th&gt;
&lt;th&gt;Cheapest fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frame delivery and format&lt;/td&gt;
&lt;td&gt;2 to 4 ms&lt;/td&gt;
&lt;td&gt;Byte count on paper, then a trace&lt;/td&gt;
&lt;td&gt;Sample YUV planes in the shader, skip the conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backpressure and queueing&lt;/td&gt;
&lt;td&gt;0 ms if correct&lt;/td&gt;
&lt;td&gt;Frame interval history at p95&lt;/td&gt;
&lt;td&gt;STRATEGY_KEEP_ONLY_LATEST plus close() in finally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference&lt;/td&gt;
&lt;td&gt;Largest single share&lt;/td&gt;
&lt;td&gt;Trace the execution path itself&lt;/td&gt;
&lt;td&gt;Confirm the backend, then lower input resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render and composite&lt;/td&gt;
&lt;td&gt;4 to 8 ms&lt;/td&gt;
&lt;td&gt;GPU timeline in Perfetto or Metal System Trace&lt;/td&gt;
&lt;td&gt;Merge passes, avoid full-frame intermediates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readback&lt;/td&gt;
&lt;td&gt;0 ms if asynchronous&lt;/td&gt;
&lt;td&gt;Look for a CPU wait beside a drained GPU queue&lt;/td&gt;
&lt;td&gt;Pixel buffer object plus a fence, or run it less often&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thermal headroom&lt;/td&gt;
&lt;td&gt;Whatever is left&lt;/td&gt;
&lt;td&gt;Multi-minute run, last minute versus first&lt;/td&gt;
&lt;td&gt;Lower resolution or frame rate under sustained load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those figures are my own starting budgets. I have yet to see a device leave all six of them intact, which is the point of filling in the measured column beside them. The gap between the two columns is the article you should be writing about your own app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this usually lands
&lt;/h2&gt;

&lt;p&gt;In most stuttering camera pipelines I have looked at, the effect itself was cheap. The time went to a frame copied into a format nothing needed, or queued when it should have been dropped, or read back synchronously, or executed somewhere nobody checked, and none of it surfaced in a 30 second run on a cool phone.&lt;/p&gt;

&lt;p&gt;Do the two divisions and the byte count before you open a profiler. They cost a minute and they routinely tell you the answer, or at least tell you which of the five places to look in first.&lt;/p&gt;

</description>
      <category>android</category>
      <category>ios</category>
      <category>performance</category>
      <category>testing</category>
    </item>
    <item>
      <title>6 Background Removal API Tests I Run Before Shipping Video</title>
      <dc:creator>Dave Gordon</dc:creator>
      <pubDate>Fri, 21 Aug 2026 13:33:50 +0000</pubDate>
      <link>https://dev.to/dave_gordon/6-background-removal-api-tests-i-run-before-shipping-video-a4a</link>
      <guid>https://dev.to/dave_gordon/6-background-removal-api-tests-i-run-before-shipping-video-a4a</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;Real-time background removal is a frame budget problem before it is a quality problem. &lt;/p&gt;

&lt;p&gt;Six tests, in this order: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;- does it fit the budget on your worst supported phone, &lt;/li&gt;
&lt;li&gt;- did the run actually use the accelerator you think it did, &lt;/li&gt;
&lt;li&gt;- what happens at the hair line, &lt;/li&gt;
&lt;li&gt;- does the mask flicker between frames, &lt;/li&gt;
&lt;li&gt;- what breaks when the lighting is real, &lt;/li&gt;
&lt;li&gt;- and what does it cost you once you count platforms instead of license fees. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most shortlists lose half their candidates on tests 1 and 5.&lt;/p&gt;

&lt;p&gt;I have swapped segmentation backends three times across two camera apps, and every swap started the same way: the library was fine in the sample project and came apart on a real device with a real user in front of it. So I stopped evaluating these things by looking at output stills.&lt;/p&gt;

&lt;p&gt;This is the harness, not the results table. I am not publishing my numbers here, because my numbers are about my pipeline and my support matrix, and neither of those is yours. A frame budget is derived from your capture rate and your effects stack, and a mid range phone in one support range is a flagship in another. &lt;/p&gt;

&lt;p&gt;What transfers is the setup and the order the tests run in. Copy the rig, run your own candidates through it, and the numbers you get will actually apply to the thing you are shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rig
&lt;/h2&gt;

&lt;p&gt;Before the first test, fix these so the results mean something across candidates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three device tiers, named, not averaged
&lt;/h3&gt;

&lt;p&gt;A current or previous cycle flagship, a mid range phone around three years old, and the cheapest device with real share in your own analytics. Record the model, OS version and chipset for each. Score every test per device. An average across the matrix hides the phone your median user is holding, which is the only phone the decision actually turns on.&lt;/p&gt;

&lt;h3&gt;
  
  
  One app state, held constant
&lt;/h3&gt;

&lt;p&gt;Camera open, your effects stack running, screen at full brightness, no other app in the foreground. Candidates measured in different app states are not comparable, and the tempting shortcut of benchmarking inference in isolation produces the number that looks best and matters least.&lt;/p&gt;

&lt;h3&gt;
  
  
  A warm device, and a stated warm-up
&lt;/h3&gt;

&lt;p&gt;Decide how long the app records before you take the sustained reading, write that duration down, and use the same one every time. Thermal state is the variable that quietly decides half of these comparisons.&lt;/p&gt;

&lt;h3&gt;
  
  
  One fixed clip set
&lt;/h3&gt;

&lt;p&gt;The same recordings, reused for every candidate: a plain wall clip, a still subject clip, and the lighting cases from Test 5. New footage per candidate means you are scoring the footage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pinned versions
&lt;/h3&gt;

&lt;p&gt;SDK or model version for every candidate, written next to every result. Vendors ship model updates that move these numbers, and a result without a version cannot be reproduced or re-checked later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the frame budget decide this before quality does?
&lt;/h2&gt;

&lt;p&gt;Most background removal comparisons open with mask quality. That is the wrong end. If inference does not fit inside a frame, quality is irrelevant, because you are already dropping frames and the preview stutters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work out your own budget before you look at a single demo:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take your target capture rate. 30 fps for most social and editor video, 60 fps if the feature has to feel like the native camera.&lt;/li&gt;
&lt;li&gt;Divide 1,000 ms by that rate. 30 fps gives you 33.3 ms per frame. 60 fps gives you 16.7 ms.&lt;/li&gt;
&lt;li&gt;Subtract the rest of the pipeline. Camera capture, color space conversion, your own filters or effects, the render pass, and encoding all take a slice.&lt;/li&gt;
&lt;li&gt;What is left is your segmentation budget. Run that subtraction rather than assuming you have the whole frame, because in the pipelines I have worked in, the remainder has never been more than half.&lt;/li&gt;
&lt;li&gt;Compare that number against measured inference time on your slowest supported device, not your average one and not the phone on your desk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Write that number down. Every test below is scored against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 1: Does it hold the budget on your worst target device?
&lt;/h2&gt;

&lt;p&gt;Run the candidate on the cheapest and oldest tier, in the app state fixed above.&lt;/p&gt;

&lt;p&gt;Three things to capture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cold inference time
&lt;/h3&gt;

&lt;p&gt;The first frames after model load are always slower. If your feature is triggered mid session, users feel this directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sustained inference time after thermal throttling
&lt;/h3&gt;

&lt;p&gt;This is the number that matters and the one almost nobody publishes. Measure it rather than extrapolating from the cold figure, and expect the two to be far enough apart that the cold figure is useless for capacity planning. If a vendor publishes one inference time without saying whether the device was warm, assume it is the cold one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory ceiling
&lt;/h3&gt;

&lt;p&gt;Segmentation model plus video buffers plus encoder is where low RAM Android devices start getting killed in the background. Watch dumpsys meminfo for your process across a long recording rather than reading a peak off a profiler graph once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 2: Did the run you just measured actually use the accelerator you think it did?
&lt;/h2&gt;

&lt;p&gt;This test exists because Test 1 produces a plausible looking number whether or not it measured the pipeline you intended.&lt;/p&gt;

&lt;p&gt;The GPU delegate fails to initialise, the library falls back to CPU without raising anything to your code, the run completes, and you write down a figure that describes a completely different execution path. Nothing in the output tells you. The mask still looks correct, the app does not crash, and the number is wrong in a direction that will not reproduce on the next build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: Failed to apply GPU delegate.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that line is in your logs, every timing you collected in that session is CPU timing. Two habits fix this permanently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assert the execution path per run. Log which delegate or backend was actually created, and discard any run where the assertion is missing rather than any run that looks slow. Slow runs are data. Runs of unknown provenance are not.&lt;/li&gt;
&lt;li&gt;Fail closed. A benchmark harness that silently completes when initialisation failed is worse than one that crashes, because it produces numbers you will act on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same applies to any library that quietly reduces input resolution or frame stride under load. If the candidate has an adaptive mode, either turn it off for the measurement or record what it chose, because otherwise you are comparing two vendors that each decided independently what to sacrifice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 3: What happens at the hair line?
&lt;/h2&gt;

&lt;p&gt;Every segmentation model produces a clean silhouette. The difference between them lives in about forty pixels around the edge.&lt;/p&gt;

&lt;p&gt;Use the plain wall clip and score four cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loose or curly hair. Naive segmentation cuts a helmet shape and drops the strands entirely.&lt;/li&gt;
&lt;li&gt;Glasses frames, especially thin metal ones, which models frequently classify as background and slice through.&lt;/li&gt;
&lt;li&gt;An object held in hand, like a mug or a phone. Person only models will amputate it. Whether that counts as a bug depends on your use case, so decide deliberately rather than finding out in review.&lt;/li&gt;
&lt;li&gt;The gap between arm and torso when someone puts a hand on their hip. Soft mask models tend to fill it in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Score these against a hard edged replacement background, not a blur. Blur hides everything, which is exactly why so many demos use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 4: Does the mask flicker between frames?
&lt;/h2&gt;

&lt;p&gt;This is the failure mode that gets caught in QA rather than in evaluation, and it is the most expensive one to fix late.&lt;/p&gt;

&lt;p&gt;A per frame image segmentation model has no memory of the previous frame. Feed it video and the mask boundary jitters frame to frame even when the subject is completely still. The stills look perfect. The clip looks cheap.&lt;/p&gt;

&lt;p&gt;Two ways a library can address it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temporal architecture
&lt;/h3&gt;

&lt;p&gt;Robust Video Matting is the reference example, in that it carries state between frames with a recurrent design instead of treating each one independently. Check the license before you get attached to it: the code was re released under GPL-3.0 in September 2021, which for most proprietary mobile apps means you are studying the architecture rather than shipping the repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your own smoothing
&lt;/h3&gt;

&lt;p&gt;You can apply temporal filtering to a per frame mask yourself, but you are trading latency for stability, and that trade comes straight out of the budget you calculated above.&lt;/p&gt;

&lt;p&gt;The test is simple. Take the still subject clip, thirty seconds of someone barely moving, then step through the export frame by frame and watch the boundary. If it breathes, you either fix it or you ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 5: What breaks when the room does?
&lt;/h2&gt;

&lt;p&gt;Evaluation footage is always shot in good light. Your users are on a couch at 9pm with a window behind them.&lt;/p&gt;

&lt;p&gt;Run each candidate through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backlight, with a bright window behind the subject. Classic failure: the person is segmented as background.&lt;/li&gt;
&lt;li&gt;Low light, where sensor noise destabilises the edge and Test 4 gets much worse.&lt;/li&gt;
&lt;li&gt;A busy background with furniture, a doorway, or a poster of a person on the wall.&lt;/li&gt;
&lt;li&gt;Two people in frame, where some models pick one, some merge both, and some alternate between the two across frames.&lt;/li&gt;
&lt;li&gt;Movement toward and away from the camera, which changes subject scale and is where fixed input resolutions show their limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you automate one part of this harness, automate this one. It is the section that regresses silently when a vendor ships a model update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 6: What does it actually cost you per platform?
&lt;/h2&gt;

&lt;p&gt;The license fee is the visible number and usually not the deciding one. Count the real cost like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List every platform you ship on. iOS, Android, web, desktop, and any framework layer such as React Native or Flutter.&lt;/li&gt;
&lt;li&gt;Check what the candidate covers natively. Apple's Vision framework offers person segmentation with selectable quality levels, and it is iOS and macOS only, so an Android build needs a second solution and a second set of edge case behaviour to test.&lt;/li&gt;
&lt;li&gt;Multiply the platforms you have to solve separately by the integration and QA time for one platform. That multiplier, not the sticker price, is where the budget goes.&lt;/li&gt;
&lt;li&gt;Add the ongoing cost. Two segmentation backends means two model update cycles, two sets of device regressions, and visible quality differences between your iOS and Android users.&lt;/li&gt;
&lt;li&gt;Check the deployment model, which is where the arithmetic gets decisive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Can you use a cloud background removal API for live video?
&lt;/h3&gt;

&lt;p&gt;Not realistically. Cloud background removal is priced per image and needs an upload per frame, so a 30 fps preview is 30 requests per second per user with a network round trip on every one. That is the right tool for batch photo processing and the wrong one for a live camera preview, and it fails on arithmetic before you get to quality.&lt;/p&gt;

&lt;p&gt;For the free, on device end of the shortlist, &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/vision/image_segmenter" rel="noopener noreferrer"&gt;MediaPipe Image Segmenter&lt;/a&gt; is Apache 2.0, and &lt;a href="https://developers.google.com/ml-kit/vision/selfie-segmentation" rel="noopener noreferrer"&gt;ML Kit selfie segmentation&lt;/a&gt; is a closed source SDK that is free to use. Both run on device across iOS and Android, which makes them the honest baseline. Anything commercial has to beat them on the tests above by enough to justify the line item, and often it does, because Tests 1, 4 and 5 are where a general purpose model gives ground to one tuned for video.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scorecard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;th&gt;Pass condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Frame budget on worst device&lt;/td&gt;
&lt;td&gt;Cold and sustained inference time, memory ceiling&lt;/td&gt;
&lt;td&gt;Sustained time fits the segmentation budget on the slowest supported device, not the average one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Execution path&lt;/td&gt;
&lt;td&gt;Whether the accelerator you configured is the one that ran&lt;/td&gt;
&lt;td&gt;Delegate asserted in the log on every run, runs without the assertion discarded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Hair line&lt;/td&gt;
&lt;td&gt;Edge quality on hair, thin frames, held objects, arm to torso gaps&lt;/td&gt;
&lt;td&gt;Scored against a hard edged background, not a blur&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Temporal stability&lt;/td&gt;
&lt;td&gt;Mask jitter across frames on a still subject&lt;/td&gt;
&lt;td&gt;Boundary holds frame to frame, or the smoothing cost is inside the budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Real conditions&lt;/td&gt;
&lt;td&gt;Backlight, low light, busy background, two people, scale change&lt;/td&gt;
&lt;td&gt;No condition drops below your quality floor, and the suite is automated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. Cost per platform&lt;/td&gt;
&lt;td&gt;Platforms solved separately, integration and QA multiplier, deployment model&lt;/td&gt;
&lt;td&gt;One integration covers your matrix, or the second one is budgeted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pick your slowest supported device and your worst lighting scenario first, then evaluate. Run these six in order and you will usually eliminate half the shortlist before pricing comes up, and you will eliminate it for reasons you can put in front of a product manager.&lt;/p&gt;

&lt;p&gt;If you run this harness on something I have not tried, I would like to hear what broke.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>android</category>
      <category>ios</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
