From b2288ad9859b9496e01346466f7d1a8008c2cf11 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:25:52 +0300 Subject: [PATCH 1/5] Add weekly release blog series --- .../blog/carplay-android-auto-codename-one.md | 142 +++++++++++++ .../blog/commerce-secrets-without-iap-tax.md | 150 ++++++++++++++ .../content/blog/motion-input-form-factors.md | 168 ++++++++++++++++ .../blog/one-codebase-more-surfaces.md | 141 +++++++++++++ .../content/blog/versioned-builds-master.md | 105 ++++++++++ .../blog/videoio-audio-mixer-whisper.md | 190 ++++++++++++++++++ docs/website/layouts/_default/single.html | 1 + .../layouts/partials/blog-post-nav.html | 30 +++ .../carplay-android-auto-codename-one.jpg | Bin 0 -> 91323 bytes .../android-auto-grid.png | Bin 0 -> 18198 bytes .../car-sim-grid.png | Bin 0 -> 19045 bytes .../car-sim-list.png | Bin 0 -> 29564 bytes .../carplay-list.png | Bin 0 -> 30109 bytes .../blog/commerce-secrets-without-iap-tax.jpg | Bin 0 -> 46545 bytes .../commerce.png | Bin 0 -> 128895 bytes .../secrets.png | Bin 0 -> 168746 bytes .../static/blog/motion-input-form-factors.jpg | Bin 0 -> 143458 bytes .../motion-showcase.png | Bin 0 -> 152335 bytes .../blog/one-codebase-more-surfaces.jpg | Bin 0 -> 132252 bytes .../static/blog/versioned-builds-master.jpg | Bin 0 -> 81220 bytes .../blog/videoio-audio-mixer-whisper.jpg | Bin 0 -> 61713 bytes .../videoio-decoded-frames.png | Bin 0 -> 190513 bytes 22 files changed, 927 insertions(+) create mode 100644 docs/website/content/blog/carplay-android-auto-codename-one.md create mode 100644 docs/website/content/blog/commerce-secrets-without-iap-tax.md create mode 100644 docs/website/content/blog/motion-input-form-factors.md create mode 100644 docs/website/content/blog/one-codebase-more-surfaces.md create mode 100644 docs/website/content/blog/versioned-builds-master.md create mode 100644 docs/website/content/blog/videoio-audio-mixer-whisper.md create mode 100644 docs/website/layouts/partials/blog-post-nav.html create mode 100644 docs/website/static/blog/carplay-android-auto-codename-one.jpg create mode 100644 docs/website/static/blog/carplay-android-auto-codename-one/android-auto-grid.png create mode 100644 docs/website/static/blog/carplay-android-auto-codename-one/car-sim-grid.png create mode 100644 docs/website/static/blog/carplay-android-auto-codename-one/car-sim-list.png create mode 100644 docs/website/static/blog/carplay-android-auto-codename-one/carplay-list.png create mode 100644 docs/website/static/blog/commerce-secrets-without-iap-tax.jpg create mode 100644 docs/website/static/blog/commerce-secrets-without-iap-tax/commerce.png create mode 100644 docs/website/static/blog/commerce-secrets-without-iap-tax/secrets.png create mode 100644 docs/website/static/blog/motion-input-form-factors.jpg create mode 100644 docs/website/static/blog/motion-input-form-factors/motion-showcase.png create mode 100644 docs/website/static/blog/one-codebase-more-surfaces.jpg create mode 100644 docs/website/static/blog/versioned-builds-master.jpg create mode 100644 docs/website/static/blog/videoio-audio-mixer-whisper.jpg create mode 100644 docs/website/static/blog/videoio-audio-mixer-whisper/videoio-decoded-frames.png diff --git a/docs/website/content/blog/carplay-android-auto-codename-one.md b/docs/website/content/blog/carplay-android-auto-codename-one.md new file mode 100644 index 0000000000..937b01f0e2 --- /dev/null +++ b/docs/website/content/blog/carplay-android-auto-codename-one.md @@ -0,0 +1,142 @@ +--- +title: "CarPlay And Android Auto From One Codename One API" +slug: carplay-android-auto-codename-one +url: /blog/carplay-android-auto-codename-one/ +date: '2026-07-04' +author: Shai Almog +description: Codename One now projects driver-safe app screens to Apple CarPlay and Google Android Auto through one portable template API, with simulator support and zero native wiring when unused. +feed_html: 'CarPlay And Android Auto Codename One now projects driver-safe app screens to Apple CarPlay and Google Android Auto through one portable template API.' +series: ["release-2026-07-03"] +--- + +![CarPlay And Android Auto From One Codename One API](/blog/carplay-android-auto-codename-one.jpg) + +Yesterday's release post was about the bigger business line we will not cross: no royalties on IAP, ads, commerce or app revenue. This post is about one of the most concrete platform additions in that release. [PR #5281](https://github.com/codenameone/CodenameOne/pull/5281) adds Apple CarPlay and Google Android Auto support under `com.codename1.car`. + +The first thing to know is what this is not. CarPlay and Android Auto are not second screens where your normal Codename One `Form` is drawn. They are driver-safe, template-based systems. Apple and Google decide which templates are legal in a car, how many rows can appear, which app categories are allowed, and which interactions are safe while driving. + +Codename One now gives you one portable way to describe those templates. + +![A CarPlay list template generated from the portable car model](/blog/carplay-android-auto-codename-one/carplay-list.png) + +![An Android Auto grid template generated from the same portable car model](/blog/carplay-android-auto-codename-one/android-auto-grid.png) + +## The Template Model + +A car app registers a `CarApplication`. When a head unit connects, the application returns a root `CarScreen`. Each screen returns one `CarTemplate`: list, grid, pane, message, navigation, or now-playing. + +```java +import com.codename1.car.*; + +public class MyApp { + public void init(Object context) { + Car.setApplication(new MyCarApplication()); + } +} + +class MyCarApplication extends CarApplication { + public CarScreen onCreateRootScreen(CarContext context) { + return new LibraryScreen(); + } +} + +class LibraryScreen extends CarScreen { + protected CarTemplate onCreateTemplate() { + return new CarListTemplate().setTitle("Library") + .addRow(new CarRow("Now Playing") + .setText("Daft Punk - Discovery") + .setOnAction(ctx -> play())) + .addRow(new CarRow("Albums") + .setBrowsable(true) + .setOnAction(ctx -> ctx.pushScreen(new AlbumsScreen()))) + .addRow(new CarRow("Playlists") + .setBrowsable(true) + .setOnAction(ctx -> ctx.pushScreen(new PlaylistsScreen()))); + } +} +``` + +That code maps to `CPListTemplate` on CarPlay and `ListTemplate` on Android Auto. Grid items map to `CPGridButton` and `GridItem`. The now-playing template routes to the system media surface. Navigation gets a template shell with controls and ETA strips; the moving map surface is scaffolded but still being completed. + +## Navigation And Lifecycle + +`CarContext` gives you a head-unit stack: + +```java +context.pushScreen(new DetailScreen()); +context.popScreen(); +screen.invalidate(); +context.showToast("Added to queue"); +``` + +`CarScreen` has lifecycle hooks for create, resume, pause and destroy. `CarApplication` receives connect and disconnect callbacks, and you can listen globally: + +```java +Car.addConnectionListener(new CarConnectionListener() { + public void carConnected(CarContext ctx) { + startLocationStream(); + } + + public void carDisconnected() { + stopLocationStream(); + } +}); +``` + +That is a good place to start and stop work that only exists while the dashboard is connected. + +## Zero Cost When Unused + +The build system scans your bytecode. If it sees `com.codename1.car`, it injects the native wiring. If it does not, the app carries none of this. + +{{< mermaid >}} +flowchart TD + A["Your app bytecode"] --> B{"References com.codename1.car?"} + B -->|no| C["Normal iOS / Android build
no car dependencies"] + B -->|yes| D["Inject car support"] + D --> E["iOS: CarPlay scene
framework + entitlement"] + D --> F["Android: CarAppService
androidx.car.app dependency"] + E --> G["Native head unit templates"] + F --> G +{{< /mermaid >}} + +That is especially important for a framework with one codebase. A normal shopping app, game, or business tool should not gain a CarPlay dependency just because the framework knows how to build one. + +## Build Hints And Approval + +Car app categories matter. The build can inject wiring, but Apple and Google still decide whether an app is allowed into the car. + +```properties +ios.carplay.audio=true +ios.carplay.navigation=true +android.androidAuto.navigation=true +android.androidAuto.poi=true +``` + +Audio is the default CarPlay category when no iOS car category is specified. Messaging, navigation and point-of-interest categories need the matching hints. Restricted Android Auto categories and real CarPlay deployment still require platform approval and, for iOS, the relevant CarPlay entitlement on your Apple App ID. + +That is not a Codename One limitation. It is the safety model of the car platforms. + +## Simulator Head Units + +The simulator has a `Car` menu now. `Car > Connect CarPlay` and `Car > Connect Android Auto` open a simulated head-unit window that renders the same portable template tree. It is not a pixel-perfect emulator, but it gives you a fast loop for stack navigation, row caps and action callbacks. + +![The Codename One simulator rendering a CarPlay-style list head unit](/blog/carplay-android-auto-codename-one/car-sim-list.png) + +Selecting a browsable row pushes the next screen: + +![The Codename One simulator rendering a CarPlay-style grid head unit](/blog/carplay-android-auto-codename-one/car-sim-grid.png) + +That is the right level of simulation. You can build the car experience while still working in the normal desktop loop, then use Apple's CarPlay simulator or Android's Desktop Head Unit when you need to test the native projection layer. + +## Wrapping Up + +CarPlay and Android Auto support is a platform capability, not a revenue feature. There is no extra royalty because your podcast app, navigation app, or point-of-interest app reaches a dashboard. You still need Apple and Google approval for the categories they restrict, and you still need to design within the car template catalogue. Codename One now gives you the portable model and build wiring so that work lives in one codebase. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/commerce-secrets-without-iap-tax.md b/docs/website/content/blog/commerce-secrets-without-iap-tax.md new file mode 100644 index 0000000000..f7111c7d53 --- /dev/null +++ b/docs/website/content/blog/commerce-secrets-without-iap-tax.md @@ -0,0 +1,150 @@ +--- +title: "Commerce And Secrets Without An IAP Tax" +slug: commerce-secrets-without-iap-tax +url: /blog/commerce-secrets-without-iap-tax/ +date: '2026-07-06' +author: Shai Almog +description: Commerce validates receipts and normalizes entitlements, while Secrets keeps credentials out of the binary. Both are designed to fail soft, stay optional, and avoid turning IAP into a Codename One royalty. +feed_html: 'Commerce And Secrets Without An IAP Tax Commerce validates receipts and normalizes entitlements, while Secrets keeps credentials out of the binary without adding a Codename One royalty to IAP.' +series: ["release-2026-07-03"] +--- + +![Commerce And Secrets Without An IAP Tax](/blog/commerce-secrets-without-iap-tax.jpg) + +Commerce is the easiest feature in this release to misunderstand, so the first sentence has to be blunt: + +**Commerce does not replace IAP and never will.** + +Purchases still go through Apple, Google, or the payment processor you chose. Codename One does not process the payment, does not touch the money, and does not take a percentage. [PR #5300](https://github.com/codenameone/CodenameOne/pull/5300) adds infrastructure around the annoying backend work that comes after a purchase: validation, entitlement checks, subscription lifecycle, webhooks and reporting. + +That backend work is real. Anyone who has shipped subscriptions knows the trap. Buying a SKU is not the same as knowing whether the user has the right to a feature right now. Renewals, grace periods, refunds, billing retry, product changes, trials, family sharing and store server notifications all show up later. The device has one view. The store has another. Your backend usually needs a third. + +Commerce is the optional service that turns that mess into an entitlement. + +![Commerce dashboard for receipt validation, entitlements and revenue metrics](/blog/commerce-secrets-without-iap-tax/commerce.png) + +{{< mermaid >}} +flowchart TD + A["App calls CommerceManager.subscribe()"] --> B["Purchase API"] + B --> C["Apple / Google store flow"] + C --> D["Store receipt"] + D --> E["Commerce refresh()"] + E --> F["Cloud receipt validation"] + F --> G["Entitlement cache"] + G --> H["isEntitled(\"pro\")"] + C --> StoreOK["Purchase still completes even if cloud validation is unavailable"] +{{< /mermaid >}} + +## Entitlements Instead Of SKU Branches + +Your app should not need to know every SKU that grants `pro`. It should ask for `pro`. + +```java +CommerceManager cm = CommerceManager.getInstance(); +cm.setAppUserId(accountId); + +if (cm.isEntitled("pro")) { + unlockProFeatures(); +} +``` + +Purchases are still delegated to the existing `Purchase` API: + +```java +cm.subscribe("pro_monthly"); +// or +cm.purchase("remove_ads"); +``` + +After a purchase, or when the app starts, refresh off the EDT: + +```java +new Thread(() -> { + CommerceManager cm = CommerceManager.getInstance(); + cm.refresh(); + + CN.callSerially(() -> { + if (cm.isEntitled("pro")) { + unlockProFeatures(); + } + }); +}).start(); +``` + +`refresh()` validates the current receipts with the cloud when the build has a `build_key` and commerce is enabled. In a local build or simulator, it safely falls back to the normal `Purchase` path. + +## What Happens When Quota Runs Out + +This is the red-team question that matters most. If Commerce is tiered, what happens when a developer exceeds quota? + +Validation degrades. Purchases do not stop. + +`CommerceManager.isDegraded()` tells you the cloud did not return a server-validated answer. In that state, entitlement checks fall back to the platform's own receipt signal, treating the entitlement id as a subscription SKU when no cached cloud answer exists. That is less rich than server-side validation, but it is the right failure mode. A paying user should not be locked out because your account hit a validated-volume cap. + +```java +cm.refresh(); +if (cm.isDegraded()) { + Log.p("Commerce validation degraded; using store-direct fallback"); +} +``` + +Commerce is tiered because it is a backend service that can be abused: receipt validation, store API calls, lifecycle processing, webhook delivery and revenue analytics cost real infrastructure. The degradation rule is what keeps that business reality from becoming user pain. + +## What Commerce Adds + +The service can: + +- Validate receipts against Apple and Google. +- Normalize subscription state across stores. +- Track entitlements by your app user id. +- Forward lifecycle webhooks to your backend with HMAC signatures. +- Present revenue metrics such as MRR, ARR, ARPU, churn, trial conversion, cohort retention and realized LTV. + +The app-facing API remains small because the complicated part lives server-side: + +```java +CommerceManager cm = CommerceManager.getInstance(); +cm.setAppUserId(myAccountId); +cm.refresh(); +boolean active = cm.isEntitled("remove_ads"); +``` + +This is why Commerce complements IAP instead of replacing it. `Purchase` starts the transaction. Commerce answers the longer-term entitlement question. + +## Secrets + +The same PR adds `com.codename1.security.Secrets`, which solves a different but related problem: API keys do not belong in source code or in the app binary. + +![Secrets dashboard for managing app-readable secrets and server-side credentials](/blog/commerce-secrets-without-iap-tax/secrets.png) + +```java +// Run off the EDT; the first call may hit the network. +String mapsKey = Secrets.get("googlemaps.key"); +``` + +The value is fetched from the Codename One Cloud vault over TLS and cached in `SecureStorage`. `refresh(name)` forces a fresh fetch after you rotate the value server-side, and `clear(name)` drops the cached copy. + +```java +String key = Secrets.refresh("googlemaps.key"); +Secrets.clear("googlemaps.key"); +``` + +Only app-readable secrets are served to the device. Server-only credentials, such as App Store Connect keys or Google Play service account JSON used for commerce validation, stay in the vault and are never reachable from client code. + +That rule is non-negotiable: do not check API keys into source, do not paste them into snippets, and do not embed server credentials in the binary. If the app can read a secret, a determined attacker can eventually extract it. Secrets reduces exposure and makes rotation easier; it does not turn a client app into a secure server. + +## The Boundary + +Commerce and Secrets are both cloud features, but they sit on different sides of the volume line. Secrets is low volume and enabled for everyone. Commerce is tiered because validation and analytics can create real backend load. + +The important boundary is not tiering. The boundary is lock-in. You can still use the raw `Purchase` API. You can still build your own receipt backend. You can still ship an app that sells subscriptions without giving Codename One a revenue share. + +Commerce is there to remove backend pain, not to insert a toll booth. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/motion-input-form-factors.md b/docs/website/content/blog/motion-input-form-factors.md new file mode 100644 index 0000000000..ee313d9f5c --- /dev/null +++ b/docs/website/content/blog/motion-input-form-factors.md @@ -0,0 +1,168 @@ +--- +title: "Motion, Stylus, Trackpads And Foldables In The Core" +slug: motion-input-form-factors +url: /blog/motion-input-form-factors/ +date: '2026-07-05' +author: Shai Almog +description: "Two new core APIs expose the hardware modern apps actually see: motion sensors and gestures on one side, rich pointer detail, wheel events, stylus input, trackpads and foldable posture on the other." +feed_html: 'Motion, Stylus, Trackpads And Foldables Motion sensors, gestures, pointer detail, wheel events, stylus input, trackpads and foldable posture are now core Codename One APIs.' +series: ["release-2026-07-03"] +--- + +![Motion, Stylus, Trackpads And Foldables In The Core](/blog/motion-input-form-factors.jpg) + +For a long time, mobile meant a touch screen and a few platform-specific escape hatches. That is no longer enough. A Codename One app can run on phones, tablets, desktops, watches, TVs, browsers, foldables, touch laptops, external displays, cars and devices with pens, mice and trackpads. + +[PR #5310](https://github.com/codenameone/CodenameOne/pull/5310) and [PR #5309](https://github.com/codenameone/CodenameOne/pull/5309) make that hardware visible through core APIs instead of cn1libs and platform branches. + +![Motion sensor showcase running in the Codename One test app](/blog/motion-input-form-factors/motion-showcase.png) + +## Motion Sensors And Gestures + +The new `com.codename1.sensors` package exposes accelerometer, gyroscope and magnetometer readings. It also derives gravity, linear acceleration and orientation in the core when the platform does not provide them directly. + +```java +MotionSensorManager sensors = MotionSensorManager.getInstance(); +MotionSensor accel = sensors.getSensor(MotionSensorManager.TYPE_ACCELEROMETER); + +if (accel != null) { + MotionSensorListener listener = new MotionSensorListener() { + public void motionReceived(MotionEvent evt) { + label.setText(evt.getX() + ", " + evt.getY() + ", " + evt.getZ()); + label.getParent().revalidate(); + } + }; + accel.addListener(listener); +} +``` + +The manager itself is always available. Individual sensors return `null` when the hardware is unsupported, so the app can keep one code path with a small capability check. + +Gestures are built on top of the sensor stream in the core: + +```java +MotionSensorManager.getInstance().addGestureListener( + GestureEvent.TYPE_SHAKE, + new GestureListener() { + public void gestureDetected(GestureEvent evt) { + Dialog.show("Shaken", "You shook the device", "OK", null); + } + }); +``` + +The recognized set is practical rather than exotic: shake, flip face down, flip face up, tilt left, tilt right, tilt forward, tilt backward, pick up and free fall. Because recognition lives in `GestureEngine`, every port that exposes an accelerometer gets the same behavior. + +Sampling is reference counted. The hardware powers on while listeners exist and powers down when they are removed. On iOS you still need `ios.NSMotionUsageDescription` with a user-facing reason for motion access. + +## Rich Pointer Detail + +Classic pointer callbacks tell you where a press happened. Modern input asks more questions: was it a mouse, a finger, a stylus, or an eraser? Which button? What pressure? Was the pen tilted? Was the pointer hovering? + +`PointerEvent` is the snapshot attached to pointer action events: + +```java +myComponent.addPointerPressedListener(e -> { + PointerEvent pe = e.getPointerEvent(); + if (pe.isSecondaryButton()) { + showContextMenu(pe.getX(), pe.getY()); + } + drawPreview(pe.getX(), pe.getY(), pe.getPressure()); +}); +``` + +You can also poll the current dispatch state through `CN`: + +```java +int button = CN.getPointerButton(); +float pressure = CN.getPointerPressure(); +boolean pen = CN.isStylusPointer(); +PointerEvent current = CN.getCurrentPointerEvent(); +``` + +On plain touch screens these values have safe defaults: primary button, pressure `1.0`, touch or unknown pointer type. Existing apps keep working. + +## Wheel, Trackpad And Context Menus + +`WheelEvent` is now the cross-platform scroll-gesture event. It handles mouse wheels, precision trackpads, horizontal scrolling, iOS/iPadOS pointer devices and the Apple Watch Digital Crown through the same path. + +```java +canvas.addMouseWheelListener(e -> { + WheelEvent w = (WheelEvent)e; + if (w.isControlDown()) { + zoom(w.getDeltaY()); + w.consume(); + } +}); +``` + +Consuming the event suppresses the default scroll. That is the difference between "wheel scrolls the container" and "control plus wheel zooms a design surface." + +For context menus, use the higher-level listener: + +```java +label.addContextMenuListener(e -> { + e.consume(); + showMyContextMenu(e.getX(), e.getY()); +}); +``` + +It fires for right click, stylus barrel button and long press, which is exactly the kind of cross-platform behavior app code should not have to rediscover. + +Trackpad magnify and rotate gestures route to component methods: + +```java +Container photo = new Container() { + protected boolean pinch(float scale) { + zoom(scale); + return true; + } + + protected boolean rotation(float radians) { + rotate(radians); + return true; + } +}; +``` + +## Stylus And Foldables + +Stylus support exposes pressure, tilt, contact size and eraser type. A drawing surface can listen only to stylus events: + +```java +drawingArea.addStylusListener(e -> { + PointerEvent pe = e.getPointerEvent(); + float width = 1f + pe.getPressure() * 9f; + if (pe.isEraser()) { + erase(pe.getX(), pe.getY()); + } else { + drawPoint(pe.getX(), pe.getY(), width); + } +}); +``` + +Foldables use `DevicePosture`: + +```java +DevicePosture posture = CN.getDevicePosture(); +if (posture.isFoldable() && posture.isTableTop()) { + layoutForTableTop(posture.getFoldBounds(null)); +} + +CN.addPostureListener(e -> relayoutForPosture(CN.getDevicePosture())); +``` + +On Android, foldable support is opt-in with `android.foldableSupport=true`, so apps that do not need the AndroidX window library do not carry it. The simulator can generate posture changes for testing. + +## Why These Belong Together + +Motion sensors and pointer metadata look unrelated until you build a real app. A drawing app wants stylus pressure, trackpad zoom, mouse wheel panning and maybe device tilt. A game wants accelerometer gestures and external display awareness. A media app wants foldable tabletop layout and trackpad scrubbing. These are not separate platforms anymore. They are one app running on hardware that keeps changing shape. + +The new APIs are additive, default-safe and core-owned. That is the part that matters. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/one-codebase-more-surfaces.md b/docs/website/content/blog/one-codebase-more-surfaces.md new file mode 100644 index 0000000000..9f4a6f40f3 --- /dev/null +++ b/docs/website/content/blog/one-codebase-more-surfaces.md @@ -0,0 +1,141 @@ +--- +title: "One Codebase, More Surfaces: Cars, Sensors, Commerce, Video And Builds" +slug: one-codebase-more-surfaces +url: /blog/one-codebase-more-surfaces/ +date: '2026-07-03' +author: Shai Almog +description: "This week's release pushes Codename One beyond the phone rectangle: car dashboards, motion sensors, desktop-class input, foldables, cloud entitlements, versioned builds, and real video generation from one codebase." +feed_html: 'One Codebase, More Surfaces This release pushes Codename One beyond the phone rectangle: car dashboards, motion sensors, desktop-class input, foldables, cloud entitlements, versioned builds, and real video generation from one codebase.' +series: ["release-2026-07-03"] +--- + +![One Codebase, More Surfaces: Cars, Sensors, Commerce, Video And Builds](/blog/one-codebase-more-surfaces.jpg) + +This week's release is about Codename One apps showing up in more places without turning into separate projects. + +A phone screen is still the center of most apps, but it is no longer the whole app. The same product may need a CarPlay list, an Android Auto grid, a foldable tabletop layout, a stylus drawing surface, trackpad zoom, motion gestures, a server-validated subscription, a pinned production build, and a generated video clip for sharing or support. + +Those are not one feature. They are the shape of modern app work. The interesting part is whether a cross-platform stack can expose those surfaces without pushing developers back into native forks for every serious use case. + +That is the story this week. + +Codename One now has core APIs for car projection, motion sensors, richer pointer input, foldable posture, frame-accurate video, sample-accurate PCM mixing, and timed subtitles. It also has cloud-side pieces for commerce validation, secrets, and versioned builds. Some of this is open source framework code. Some of it is optional service infrastructure. The boundary matters. + +{{< mermaid >}} +flowchart LR + A["One Codename One app"] --> B["Phone / tablet UI"] + A --> C["Car dashboard templates"] + A --> D["Motion and input hardware"] + A --> E["Foldables and desktop windows"] + A --> F["Generated media"] + A --> G["Optional cloud services"] + G --> H["Receipt validation, secrets, builds"] + H -. "support and capacity, not revenue share" .-> A +{{< /mermaid >}} + +The red-team question is not "how many bullet points shipped?" It is "where does this force the app developer into a trap?" + +For the core APIs, the answer should be boring: if a sensor, stylus, foldable hinge, car head unit, or video backend is unavailable, the app can detect that and adapt. For the cloud APIs, the answer has to be more explicit: **Codename One does not take a percentage of your in-app purchases, subscriptions, ad revenue, commerce revenue, or app revenue**. Commerce validates receipts and entitlements. It does not replace IAP, does not process the payment, and does not become a toll booth. If Commerce quota is exhausted, validation degrades; store purchases still go through and users are not blocked from buying. + +Here is what shipped. + +## CarPlay and Android Auto + +[PR #5281](https://github.com/codenameone/CodenameOne/pull/5281) adds a portable `com.codename1.car` API for Apple CarPlay and Google Android Auto. The important caveat is that car platforms are template-based. They do not allow an app to draw an arbitrary Codename One `Form` on the dashboard. You describe a driver-safe list, grid, message, pane, navigation, or now-playing template, and Codename One maps that to `CPTemplate` on CarPlay and `androidx.car.app` templates on Android Auto. + +![CarPlay list template rendered by the Codename One car API](/blog/carplay-android-auto-codename-one/carplay-list.png) + +![Android Auto grid template rendered by the Codename One car API](/blog/carplay-android-auto-codename-one/android-auto-grid.png) + +The API is zero cost when unused. Referencing `com.codename1.car` is what tells the build to inject CarPlay scenes, entitlements, Android Auto services, and the AndroidX dependency. Apps that never touch the package do not carry that code. Tomorrow's post walks through the template model, the simulator head unit, and the approvals you still need from Apple and Google. + +## Motion, Input, And Real Hardware + +[PR #5310](https://github.com/codenameone/CodenameOne/pull/5310) adds `com.codename1.sensors`, a cross-platform motion API with accelerometer, gyroscope, magnetometer, derived gravity, linear acceleration, orientation, and common gestures such as shake, flip, tilt, pick up, and free fall. This replaces the old external `sensors-codenameone` approach with a core API and a core gesture engine. + +[PR #5309](https://github.com/codenameone/CodenameOne/pull/5309) fills in the other side of modern hardware: rich pointer events, mouse buttons, wheel and trackpad scrolling, stylus pressure and tilt, foldable posture, desktop windowing, and external display awareness. + +![Motion sensor showcase running in the Codename One test app](/blog/motion-input-form-factors/motion-showcase.png) + +Together they make Codename One less phone-only. A canvas can tell the difference between a finger, a mouse and a stylus. A foldable can split layout around a hinge. A trackpad pinch can zoom the same component as a mobile pinch. Sunday's post covers the full hardware story. + +## Commerce And Secrets + +[PR #5300](https://github.com/codenameone/CodenameOne/pull/5300) adds the Commerce SDK and the Secrets API. + +Commerce is optional infrastructure around IAP. It answers the backend question apps usually end up writing themselves: "does this user have this entitlement right now?" It can validate receipts, normalize subscription state across Apple and Google, send lifecycle webhooks to your backend, and show revenue metrics. It does not replace `Purchase`, it delegates to it. + +```java +CommerceManager cm = CommerceManager.getInstance(); +cm.setAppUserId(accountId); +cm.subscribe("pro_monthly"); + +new Thread(() -> { + cm.refresh(); + if (cm.isEntitled("pro")) { + unlockProFeatures(); + } +}).start(); +``` + +Secrets is deliberately lower volume and is enabled for everyone. It fetches app-readable secrets from the cloud vault at runtime and caches them in `SecureStorage`, so API keys do not need to live in code or in the binary. Server-only credentials, such as App Store or Google Play keys used by commerce validation, stay server-side and are not served to the app. + +Monday's post is mostly a red-team pass over Commerce: what it does, what it refuses to do, what happens when quota is exhausted, and why optional validation is not an IAP tax. + +![Commerce dashboard for receipt validation and entitlement tracking](/blog/commerce-secrets-without-iap-tax/commerce.png) + +![Secrets dashboard for cloud-managed app secrets](/blog/commerce-secrets-without-iap-tax/secrets.png) + +## Versioned Builds Are Back + +Versioned builds are back, and the model is better suited to Maven than the old Ant-era point release scheme. You can pin a cloud build to a released Codename One version, or build against `master` when you need to verify an unreleased fix. + +```properties +build.cn1Version=master +``` + +That `master` option is in some ways better than the nightly-build request we resisted for years. You are not waiting for an artificial daily package. You are asking the build server to use the current development head. + +This is limited by subscription tier because old versions create support churn, not because fetching a few artifacts is the business model. The further back a build goes, the harder it is to diagnose a regression against the current code. The new model still opens versioned builds much further down the account ladder than before, including limited access for basic/free usage, while giving paying teams the longer support windows they actually need. Tuesday's post covers the tradeoff. + +## Video, Audio, And Subtitles + +[PR #5315](https://github.com/codenameone/CodenameOne/pull/5315) adds `VideoIO`: cross-platform video encode and frame-accurate decode using native codecs. It can encode app-rendered frames plus audio into MP4/WebM/MOV/MKV style containers, enumerate codecs, decode exact frames, resample variable-frame-rate clips, and expose the audio track as PCM. + +[PR #5317](https://github.com/codenameone/CodenameOne/pull/5317) adds a sample-accurate `AudioMixer` for combining PCM tracks on one clock. [PR #5319](https://github.com/codenameone/CodenameOne/pull/5319) adds timed Whisper transcription, so generated videos can get SRT or VTT captions instead of plain text transcripts. + +![Frame-accurate VideoIO decode output from the Codename One test app](/blog/videoio-audio-mixer-whisper/videoio-decoded-frames.png) + +Wednesday's post shows how these pieces fit: render frames, mix audio, encode a video, decode frames back out, and attach timed captions. + +## Smaller But Important + +There are four smaller changes worth calling out: + +- **JavaSE upstream JCEF and bundled ffmpeg media.** [PR #5294](https://github.com/codenameone/CodenameOne/pull/5294) moves the simulator away from our home-built Chromium 84 CEF fork and onto upstream prebuilt JCEF. We used to build our own CEF fork mostly to keep H.264 media working. That build was long, fragile and kept us behind. The new approach uses upstream JCEF for the browser and bundled ffmpeg for the Codename One `Media` API. +- **Skin Designer now builds with our JavaScript port.** [PR #5303](https://github.com/codenameone/CodenameOne/pull/5303) moves Skin Designer to the local ParparVM JavaScript target. Initializr and Playground already moved; the console is now the last TeaVM internal tool. This migration did expose a regression, but the JavaScript port is still moving toward production. +- **MorphTransition grew up.** [PR #5314](https://github.com/codenameone/CodenameOne/pull/5314) adds opacity, rotation, scale, deterministic scrubbing, and arbitrary rendered elements. This matters for UI polish and also for frame export, because scrubbing lets the same transition be rendered into generated video. +- **Timed Whisper transcription.** [PR #5319](https://github.com/codenameone/CodenameOne/pull/5319) also deserves a second mention because timed segments turn transcription into subtitles. The cn1lib can render SRT and VTT payloads from segment timestamps. + +## Upcoming Posts + +- **Saturday.** {{< post-link path="/blog/carplay-android-auto-codename-one" text="CarPlay and Android Auto from one template API" >}}. PR [#5281](https://github.com/codenameone/CodenameOne/pull/5281). +- **Sunday.** {{< post-link path="/blog/motion-input-form-factors" text="Motion sensors, pointer detail, stylus, trackpads and foldables" >}}. PRs [#5310](https://github.com/codenameone/CodenameOne/pull/5310) and [#5309](https://github.com/codenameone/CodenameOne/pull/5309). +- **Monday.** {{< post-link path="/blog/commerce-secrets-without-iap-tax" text="Commerce and Secrets without an IAP tax" >}}. PR [#5300](https://github.com/codenameone/CodenameOne/pull/5300). +- **Tuesday.** {{< post-link path="/blog/versioned-builds-master" text="Versioned builds and building against master" >}}. +- **Wednesday.** {{< post-link path="/blog/videoio-audio-mixer-whisper" text="VideoIO, the PCM mixer and timed Whisper captions" >}}. PRs [#5315](https://github.com/codenameone/CodenameOne/pull/5315), [#5317](https://github.com/codenameone/CodenameOne/pull/5317), and [#5319](https://github.com/codenameone/CodenameOne/pull/5319). +- **Thursday.** {{< post-link path="/blog/game-builder-3d-dungeon" text="Game Builder Tutorial 3: a first-person 3D dungeon" >}}. The final part of the Game Builder tutorial series. + +## Wrapping Up + +The release is large because the app surface is larger now. It reaches cars, foldables, desktops, sensors, stylus devices, media pipelines, cloud validation and older build timelines. The job of the framework is to keep those surfaces connected to one codebase without hiding the tradeoffs. + +That includes the business tradeoffs. Cloud validation should help, not hold purchases hostage. Build tiers should buy capacity and support windows, not a license to keep your own revenue. Optional should mean optional. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/versioned-builds-master.md b/docs/website/content/blog/versioned-builds-master.md new file mode 100644 index 0000000000..4dca86fafa --- /dev/null +++ b/docs/website/content/blog/versioned-builds-master.md @@ -0,0 +1,105 @@ +--- +title: "Versioned Builds Are Back, And Master Builds Are Better Than Nightlies" +slug: versioned-builds-master +url: /blog/versioned-builds-master/ +date: '2026-07-07' +author: Shai Almog +description: "Versioned builds return with a Maven-era model: pin a cloud build to a released Codename One version, or build against master to verify the current development head without waiting for a nightly package." +feed_html: 'Versioned Builds Are Back Versioned builds return with a Maven-era model: pin a cloud build to a release, or build against master to verify the current development head.' +series: ["release-2026-07-03"] +--- + +![Versioned Builds Are Back, And Master Builds Are Better Than Nightlies](/blog/versioned-builds-master.jpg) + +Versioned builds are back. + +The old versioned-build story came from the Ant era. Codename One had point releases every few months, and versioning meant "build against that point release." It made sense at the time, but the platform and the build infrastructure moved on. Maven Central, faster releases, more build targets, and a much larger code surface made the old model hard to maintain. + +The new model keeps the part developers actually need: a way to stabilize and diagnose builds without being forced onto the current server-side framework every time. + +## Pin A Released Version + +By default, a cloud build uses the current Codename One release. With a versioned build, you can pin to a specific published version: + +```properties +build.cn1Version=7.0.182 +``` + +The build server fetches that version's framework artifacts and builds against them. If you also want the simulator and local compile classpath to match, set the matching `cn1.version` in your Maven project. + +This is useful in three situations: + +- You are stabilizing a production release and want fewer moving parts. +- A build started failing and you need to know whether the regression is in your app or in the framework/build server. +- You are supporting an older customer app and need to reproduce the environment it last built with. + +## Build Against Master + +The more interesting part is `master`: + +```properties +build.cn1Version=master +``` + +This builds against the current development head of Codename One. A few years ago, community developers asked for nightly or daily builds. We resisted because pushing nightly artifacts through Maven Central and support channels is messier than it sounds. A fixed nightly is also stale the moment the next fix lands. + +Building against `master` is the better developer loop for most cases. If a fix landed this morning and you need to verify it on a device, you do not wait for tomorrow's nightly. You ask the cloud build to use the current development head. + +{{< mermaid >}} +flowchart LR + A["A fix lands on master"] --> B["Framework artifacts published"] + B --> C["Your build sets build.cn1Version=master"] + C --> D["Cloud build uses current development head"] + D --> E["Verify on real target"] + E --> F["Switch back to a release for production"] +{{< /mermaid >}} + +Use this for verification, not for shipping production builds. `master` is active development. It gives you speed, not a stability promise. + +## Why It Is Tiered + +The small overhead of fetching versioned artifacts is noticeable, but that is not the real reason this is limited by account level. + +The hard cost is support. + +If a free user reports a build issue from six months ago, and another user reports one from yesterday, and an enterprise customer needs a diagnosis against a two-month-old version, support becomes a bisection problem across a moving build system. The further back the community goes, the harder every issue is to understand, reproduce and fix. + +That does not mean versioned builds should only exist at the top. The new approach opens them much further down than before, including limited access for basic/free usage. The longer windows still belong to higher tiers because those teams are the ones that need older reproducible builds and the support time that comes with them. + +The principle is simple: the tier buys a support window and capacity, not a right to keep your app revenue. + +## A Practical Workflow + +When a build starts failing after an update: + +```properties +# First, verify the last known good release. +build.cn1Version=7.0.182 +``` + +If that works, the regression is likely in the framework or build server. If it still fails, the issue is probably in your app, dependencies, native configuration, or a platform toolchain change that affects the old version too. + +When a fix lands: + +```properties +# Then verify the unreleased fix. +build.cn1Version=master +``` + +Once the fix is released, pin to the release you plan to ship with or remove the hint to follow the current default again. + +## What This Does Not Solve + +Versioned builds are not a time machine for every external dependency. Apple, Google, OS SDKs, certificates, signing rules, app store requirements, Maven repositories and native tools all move. A six-month-old framework can still be affected by a current Xcode or Android requirement. + +It also does not make `master` a nightly release channel. It is a verification channel. + +That distinction keeps the feature useful without turning support into archaeology for every build ever made. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/videoio-audio-mixer-whisper.md b/docs/website/content/blog/videoio-audio-mixer-whisper.md new file mode 100644 index 0000000000..7fa2e89f82 --- /dev/null +++ b/docs/website/content/blog/videoio-audio-mixer-whisper.md @@ -0,0 +1,190 @@ +--- +title: "VideoIO, PCM Mixing And Timed Whisper Captions" +slug: videoio-audio-mixer-whisper +url: /blog/videoio-audio-mixer-whisper/ +date: '2026-07-08' +author: Shai Almog +description: "Codename One can now generate and inspect real video: encode app-rendered frames with audio, decode exact frames back out, mix PCM on a sample clock, and turn Whisper segment timestamps into subtitles." +feed_html: 'VideoIO, PCM Mixing And Timed Whisper Captions Codename One can now generate and inspect real video: encode app-rendered frames, decode exact frames, mix PCM, and turn Whisper timestamps into subtitles.' +series: ["release-2026-07-03"] +--- + +![VideoIO, PCM Mixing And Timed Whisper Captions](/blog/videoio-audio-mixer-whisper.jpg) + +This release turns media from "play a file" into "build a file." + +[PR #5315](https://github.com/codenameone/CodenameOne/pull/5315) adds `VideoIO`, a cross-platform video subsystem that can encode app-rendered frames and audio into a standard video file, then decode an existing clip back into exact RGBA frames and PCM audio. [PR #5317](https://github.com/codenameone/CodenameOne/pull/5317) adds `AudioMixer`, a sample-accurate PCM timeline. [PR #5319](https://github.com/codenameone/CodenameOne/pull/5319) adds timed Whisper transcription segments with SRT and VTT output. + +Together, these APIs let Codename One generate proper videos across supported app platforms. + +![Frame-accurate VideoIO decode output from the Codename One test app](/blog/videoio-audio-mixer-whisper/videoio-decoded-frames.png) + +## Encoding App-Rendered Frames + +`VideoIO` is gated like other optional platform APIs: + +```java +if (!VideoIO.isSupported()) { + return; +} +``` + +Then you build a writer. Frames are ordinary `Image` instances, so the pixels can come from your UI, a game renderer, an animation scrubber, a generated chart, or a custom drawing routine. + +```java +String out = FileSystemStorage.getInstance().getAppHomePath() + "/demo.mp4"; + +VideoWriter writer = new VideoWriterBuilder() + .path(out) + .container(VideoIO.CONTAINER_MP4) + .width(720) + .height(1280) + .frameRate(30) + .videoCodec(VideoIO.CODEC_H264) + .videoBitRate(4000000) + .hasAudio(true) + .audioCodec(VideoIO.CODEC_AAC) + .sampleRate(44100) + .audioChannels(2) + .build(); + +try { + for (int i = 0; i < 90; i++) { + Image frame = renderFrame(i); + writer.writeFrame(frame, i * 1000L / 30L); + } + writer.writeAudio(pcm, 44100, 2, 0); +} finally { + writer.close(); +} +``` + +The exact codecs available are platform-dependent. `VideoIO.getAvailableEncoders()` and `getAvailableDecoders()` expose what the device actually supports, including hardware acceleration flags. + +## Frame-Accurate Decode + +The old `Media` API is a player. It can seek, but playback seek is usually key-frame based. That is not enough when you are generating thumbnails, verifying a rendered animation, extracting frames for processing, or round-tripping a generated video in a test. + +`VideoReader.frameAt(ms)` returns the exact frame at a timestamp: + +```java +VideoReader reader = VideoIO.getVideoIO().openReader(out); +try { + VideoFrame oneSecond = reader.frameAt(1000); + Image image = oneSecond.getImage(); +} finally { + reader.close(); +} +``` + +You can also resample a whole clip to a constant frame rate: + +```java +reader.readFrames(10, frame -> { + process(frame.getImage(), frame.getTimestampMillis()); + return true; +}); +``` + +And when the backend supports it, `readAudio()` returns the audio track as an `AudioBuffer`: + +```java +AudioBuffer audio = reader.readAudio(); +``` + +JavaScript currently supports frame decode and full encode, but decoded audio extraction is not wired there yet. TV, Watch and Car targets do not expose `VideoIO`. + +## PCM Mixing On One Clock + +`AudioMixer` is intentionally small. It combines interleaved float PCM tracks in the normalized `[-1, 1]` range on a single sample clock. + +```java +AudioBuffer music = new AudioBuffer(musicPcm.length); +music.copyFrom(44100, 2, musicPcm); + +AudioBuffer voice = new AudioBuffer(voicePcm.length); +voice.copyFrom(44100, 2, voicePcm); + +AudioMixer mixer = new AudioMixer(44100, 2); +mixer.addTrack(music, 0, 0.65f); +mixer.addTrack(voice, 1200, 1.0f); + +AudioBuffer mixed = mixer.mix(); +``` + +If you already know exact sample-frame offsets, use `addTrackAtFrame(...)` instead of millisecond offsets. The output clips to `[-1, 1]` and can be written through `WAVWriter` or passed into a `VideoWriter`. + +```java +float[] pcm = new float[mixed.getSize()]; +mixed.copyTo(pcm); + +WAVWriter wav = new WAVWriter(new File("mix.wav"), + mixed.getSampleRate(), + mixed.getNumChannels(), + 16); +try { + wav.write(pcm, 0, mixed.getSize()); +} finally { + wav.close(); +} +``` + +## Subtitles From Timed Whisper + +The Whisper cn1lib already returned text. The new transcription API returns timed segments: + +```java +Transcriber t = Transcriber.getDefault(); +TranscriptionResult result = t.transcribe( + new TranscriptionRequest(audioPath).setLanguage("en")); + +String srt = result.toSrt(); +String vtt = result.toVtt(); +``` + +Those segment timestamps are the missing link between "we transcribed the video" and "the video has subtitles." A generated training clip, game replay, tutorial or support recording can now get caption files automatically. + +The Whisper PR also fixed native packaging gaps: Android now ships a JNI AAR with native slices, iOS exposes timed segments, JavaSE keeps a fallback segment, Linux and Windows native bridge modules are packaged, and JavaScript is explicitly unsupported. + +## Where MorphTransition Fits + +[PR #5314](https://github.com/codenameone/CodenameOne/pull/5314) is listed as a smaller enhancement in the release post, but it connects directly to media generation. `MorphTransition` is now scrubbable: + +```java +MorphTransition transition = MorphTransition.create(300) + .snapshotMode(true) + .opacity("card", 0, 255) + .scale("card", 0.8f, 1.0f) + .rotation("card", -0.2f, 0); + +transition.setProgress(0.5); +``` + +Scrubbing means a transition can be rendered deterministically at progress `0.0`, `0.1`, `0.2`, and so on. That is exactly what a video export loop needs. + +## Platform Backends + +The backends use native media stacks rather than one portable lowest-common-denominator codec: + +- JavaSE and the simulator use bundled ffmpeg/ffprobe. +- iOS and macOS use AVFoundation. +- Android uses MediaCodec, MediaMuxer, MediaExtractor and MediaMetadataRetriever. +- Windows uses Media Foundation. +- Linux uses GStreamer. +- JavaScript uses HTML5 video/canvas decode and WebCodecs encode. + +This gives each platform the codec set and acceleration it already has. App code still starts from the same `VideoIO` facade. + +## Wrapping Up + +Video export used to be something a Codename One app handed off to a backend or a native library. Now the app can render frames, mix PCM, encode a video, decode it back for validation, and generate timed captions. That opens up tutorials, game clips, social exports, support recordings, animated explainers and media QA workflows from the same Java/Kotlin codebase. + +No special revenue model is attached to that. The app generates the media. The app owns the output. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/layouts/_default/single.html b/docs/website/layouts/_default/single.html index 0c1bb2d92e..c2b807c5bd 100644 --- a/docs/website/layouts/_default/single.html +++ b/docs/website/layouts/_default/single.html @@ -165,6 +165,7 @@

{{ .Title }}

{{- end }}