Cocos Creator Session Replay (Experimental)¶
This document describes Cocos Creator Session Replay initialization, Camera selection, performance parameters, touch privacy, and node privacy rules.
Experimental Capability
Cocos Creator Session Replay is currently an experimental capability. APIs, platform compatibility, and playback quality may change in subsequent versions. It is recommended to evaluate privacy, performance, and playback completeness in a test environment before deciding whether to use it in production.
Prerequisites¶
Session Replay depends on:
- A native Android or iOS build;
- RUM already initialized;
- A valid RUM View currently active;
- Using the Cocos SDK and native dependencies in the current SDK version combinations.
If there is no valid RUM Context, the SDK skips the current frame and does not generate replay data independent of RUM. It is recommended to enable autoTrack.scenes or manually call truewatchSdk.rum.startView() before frame capture.
Install Standalone Replay Package¶
Starting from Cocos SDK 0.1.0-alpha.6, Session Replay is provided separately by @truewatchtech/cocos-session-replay. Install exactly the same version of both packages in the Cocos project root:
npm install @truewatchtech/cocos-sdk@0.1.0-alpha.7 @truewatchtech/cocos-session-replay@0.1.0-alpha.7
npx --no-install truewatch-cocos install --project . --replay
Reopen Creator, then generate and compile the native project. For iOS projects using CocoaPods, run pod install as well. For rules on preserving SPM configuration and switches, see App Access.
The base package does not include the Replay APIs, frame capture implementation, or the Replay native dependencies. The following example saves the combined instance as the exported truewatchSdk in observability.ts; other modules reuse this instance.
Initialization¶
Note
In the code examples on this page, ... indicates that the base sdk configuration (for example, datakitUrl) has been omitted. First complete the common configuration as described in SDK Initialization; this page only shows Session Replay-related configuration.
import { truewatchSdk as baseSdk } from '@truewatchtech/cocos-sdk/creator3';
import { withSessionReplay } from '@truewatchtech/cocos-session-replay/creator3';
export const truewatchSdk = withSessionReplay(baseSdk);
truewatchSdk.start({
...,
rum: {
androidAppId: 'android-rum-app-id',
iosAppId: 'ios-rum-app-id',
},
replay: {
sampleRate: 1,
sessionOnErrorSampleRate: 0,
captureFps: 2,
maxImageDimension: 720,
imagePolicy: {
quality: 'medium',
},
touchPrivacy: 'show',
},
autoTrack: {
scenes: true,
},
});
For Creator 2, change the import entry of both packages to /creator2. Composition must happen before SDK initialization or Hybrid attach(); subsequent .replay, start({ replay }), and attach({ replay }) calls are made on the combined instance.
| Field | Type | Required | Description |
|---|---|---|---|
sampleRate |
number |
No | Session Replay session sampling rate, range 0–1 |
sessionOnErrorSampleRate |
number |
No | Error session additional sampling rate, range 0–1 |
captureFps |
number |
No | Frames captured per second; only integers 1–5 are allowed, default 1 |
maxImageDimension |
number |
No | Longest-edge pixel size of captured frames, range 1–2048; when not specified, the quality preset value is used. The medium default is 720 |
imagePolicy |
FTReplayImagePolicy |
No | Image encoding quality and per-frame size policy; enabled when explicitly passed. See supported builds below |
touchPrivacy |
show / hide |
No | Touch data privacy level; show records the positions of touch down and touch up, hide does not record touch positions. Default hide |
Invalid sampling rates, FPS, image dimensions, or traffic policies throw a TypeError or RangeError at the TypeScript layer.
Image Size Policy¶
Supported Builds
The per-frame limits below are available starting from 0.1.0-alpha.7. The released 0.1.0-alpha.6 does not include this adjustment; the on-device measurements below use a local development package that includes the adjustment and cannot be judged by its alpha.6 version number alone. For upgrade steps, see Upgrade to alpha.7.
captureFps independently controls the target frame capture frequency, while imagePolicy controls image encoding quality and per-frame size. To use per-frame limits, explicitly pass imagePolicy:
replay: {
captureFps: 3,
maxImageDimension: 720,
imagePolicy: {
quality: 'medium',
maxFrameBytes: 40 * 1024,
},
}
| Preset | Default Longest Edge | Initial Encoding Quality | Per-Image Limit |
|---|---|---|---|
low |
480 px | 0.35 | 20 KiB |
medium |
720 px | 0.45 | 40 KiB |
high |
960 px | 0.60 | 80 KiB |
imagePolicy supports the following fields:
| Field | Type | Default | Description |
|---|---|---|---|
quality |
low / medium / high |
medium |
Quality preset; does not change captureFps |
maxFrameBytes |
number |
Preset of the current quality grade | Maximum encoded bytes per image, range 1 KiB–1 MiB, must be an integer; the first frame of a new View and of an orientation change is also subject to this limit |
An explicitly set maxImageDimension and imagePolicy.maxFrameBytes override the preset values of the quality grade. Android V2 uses WebP and iOS V2 uses JPEG; when encoding exceeds the limit, quality is reduced first, then dimensions are lowered. If the image still exceeds the limit, the image is not saved, while pending touch records can still be saved.
0.1.0-alpha.7 removes the 60-second scroll budget for images, the budget-triggered frame rate reduction, and the 100 KiB first-frame burst allowance. When upgrading, remove imagePolicy.maxBytesPerMinute and imagePolicy.adaptiveCapture; these two fields are no longer part of the configuration API. captureFps is a target frequency; deduplication, frame capture, and encoding time still affect the actual output. The SDK processes only one frame at a time and skips missed capture slots when the capture time slot is exceeded; it does not queue concurrent tasks.
When imagePolicy is not passed, the legacy image path is retained with no encoded byte limit, and the per-frame limits above do not apply. Passing imagePolicy: {} applies the medium preset. Once explicitly enabled, the native V2 image interfaces are required; if these interfaces are unavailable or return oversized images, an error is reported rather than falling back to legacy interfaces that cannot guarantee size limits.
Camera Selection¶
By default, the first Camera found in the current scene is used. Multi-Camera projects should explicitly specify the Camera to replay:
import { truewatchSdk } from './observability';
export function selectReplayCamera(camera: unknown): void {
truewatchSdk.setReplayCamera(camera);
}
setReplayCamera() stores only one Camera at a time. When called multiple times, the Camera passed in the latest call overrides the previously set Camera. The SDK does not capture or composite multiple Cameras simultaneously. If the previous frame is being captured at the time of the call, the new Camera takes effect from the next capture.
After switching the main Camera, call setReplayCamera() again. If there is no available Camera in the scene, the current frame is skipped.
In 0.1.0-alpha.7, Creator 3 captures frames using a separate offscreen Camera, avoiding modification of the rendering target of the display Camera and avoiding impact on input coordinates that depend on the Camera. This adjustment does not change the single-Camera capture limit; after upgrading, verify multi-Camera behavior, screen orientation changes, and node mask rendering.
Touch Privacy¶
touchPrivacy controls whether Session Replay records touch positions:
| Mode | Behavior |
|---|---|
show |
Records touch down and touch up positions in the Replay |
hide |
Does not record touch positions; default value |
Touch collection is part of Session Replay and is independent of autoTrack.actions: even if autoTrack.actions is disabled, touchPrivacy: 'show' still records Replay touches; conversely, enabling Action auto-collection while keeping touchPrivacy: 'hide' means touch positions still do not appear in the Replay. Even when the previous and next frames are visually unchanged, pending touch operations are saved separately to the Replay.
0.1.0-alpha.7 fixes the issue in Creator 3 where touches consumed by UI controls were missing from the Replay, and preserves normal input dispatch. After enabling touchPrivacy: 'show', verify both the business response of controls such as buttons and joysticks and the touch positions in the Replay to confirm that operations are not recorded twice. This fix does not extend the collection scope of autoTrack.actions.
The current Cocos API only supports global touch privacy settings and does not support per-node touch privacy overrides. truewatchSdk.replay.setPrivacy(node, 'hide') only hides the node's visual content and does not hide touch records at that position. Sensitive pages should use touchPrivacy: 'hide' when starting Session Replay.
Touch Position Privacy
Touch coordinates may expose where users interact on sensitive pages. Only set show after completing a privacy assessment and obtaining the necessary authorization; otherwise, keep the default value hide.
Node Privacy¶
All EditBox nodes use mask by default. You can also set rules for specific nodes:
truewatchSdk.replay.setPrivacy(accountNode, 'mask');
truewatchSdk.replay.setPrivacy(secretPanelNode, 'hide');
truewatchSdk.replay.setPrivacy(publicNode, 'unmask');
| Mode | Behavior |
|---|---|
mask |
Covers the node's rectangular area with a mask color |
hide |
Hides the node's rectangular area with a solid color |
unmask |
Removes the custom rule for that node |
unmask only removes custom rules. If the node is still an EditBox, the default mask continues to apply.
Privacy regions are calculated based on the node's world-coordinate rectangle. Custom rendering, particles, shaders, RenderTextures, or visual content that extends beyond the node's bounding box are not automatically derived as privacy regions and must be checked scene by scene in integration tests.
Managing Masks Per Page¶
Rules set by setPrivacy() are bound to the passed node instances and do not switch automatically by page name or RUM View. When different pages need to mask different content, the business logic for page entry and exit should manage the rules for each page's nodes.
The following example switches between a "Diagnostics Page" and an "Action Page" in the same scene: diagnosticsPage and motionPage are the root nodes of the two pages, and privateTokenNode is an ordinary node inside the diagnostics page that needs masking. After SDK initialization or Hybrid attach() completes, call the corresponding functions on page switches:
function showDiagnostics() {
// Reapply the rule on every entry to ensure the masking rule exists before the sensitive node is shown.
truewatchSdk.replay.setPrivacy(privateTokenNode, 'mask');
motionPage.active = false;
diagnosticsPage.active = true;
}
function showMotion() {
// Hide the sensitive page first, then remove the custom rules of that page's nodes.
diagnosticsPage.active = false;
truewatchSdk.replay.setPrivacy(privateTokenNode, 'unmask');
motionPage.active = true;
}
If the action page also has sensitive nodes, set mask or hide for each of those nodes before they are shown, and clear the corresponding rules on exit. When a page contains multiple custom-masked nodes, each node must be managed individually.
Additional considerations for the page lifecycle:
- Setting only the page or parent node to
active = falsedoes not remove the registered custom rules. Hiding a page is not a substitute for callingunmask; otherwise, the mask region of the old page may affect subsequent frames. - Before a page exits or its nodes are destroyed, clear the custom rules registered for that page. This logic can be placed in the project's page manager or in the component's enable, disable, and destroy callbacks.
- Reapply the rules when re-entering a page. If a page is destroyed and rebuilt, the newly created node instances must be passed in.
- Switching RUM View names does not automatically set or clear node rules. Hybrid
enterCocos()/leaveCocos()manages collection attribution; pages still need to manage node masks themselves. unmaskonly removes the custom rule of the specified node; it does not disable global privacy protection or cancel the defaultEditBoxmask.
These rules affect the rendering of nodes in Cocos frame capture. Hybrid native pages continue to use the Session Replay privacy configuration of the host Native SDK, while touch positions are controlled separately by touchPrivacy.
During verification, cover "enter page → switch to another page → re-enter"; confirm in the Replay that sensitive content remains masked continuously and that no residual masks remain on other pages.
For more privacy recommendations, see Data and Privacy.
Runtime Behavior¶
- Uses Cocos
RenderTextureto capture RGBA frames. - When
imagePolicyis not configured, the legacy image path is retained; when explicitly configured, Android uses WebP V2 and iOS uses JPEG V2, and the actual encoded size is checked. - Privacy masks are applied before image compression.
- Frames whose content is identical or approximately static are skipped; changes to the View, frame dimensions, or privacy rules force a change frame.
- When an image is skipped due to deduplication or encoding size, pending touch records are still saved separately.
- When the previous frame is still being processed, no new frame is processed concurrently.
- A single frame capture or encoding failure only discards the current frame and does not stop subsequent Session Replay collection.
- Temporary images are deleted from the application temporary directory after being written to the Native SDK.
Traffic Calculation¶
The following only calculates the image payload of a single recorded session; 1 KiB = 1024 bytes, 1 MiB = 1048576 bytes. Replay Segments, touch metadata, upload protocol overhead, and retries must be counted separately.
Calculation Method¶
Measured image traffic (MiB/min) = total bytes of post-deduplication images ÷ corresponding recording duration (seconds) × 60 ÷ 1048576
Planned image traffic (MiB/min) ≈ average encoded bytes per image × expected images saved per minute ÷ 1048576
Full-frame image theoretical limit (MiB/min) ≈ maxFrameBytes × captureFps × 60 ÷ 1048576
Downloads generated by repeated playback are deduplicated by image resource ID; multiple records reusing the same image do not accumulate the resource size repeatedly. The statistics duration and image set must correspond; the total image size of the entire Replay cannot be divided by the duration of only one of its Views. What is downloaded from the cloud is the image payload size. The real total upload traffic on the device must be measured on the device side.
Full-Frame Theoretical Limits for Six Configurations¶
| Preset | Per-Frame Limit | 2 FPS Image Theoretical Limit | 3 FPS Image Theoretical Limit |
|---|---|---|---|
low |
20 KiB | 2.34 MiB/min | 3.52 MiB/min |
medium |
40 KiB | 4.69 MiB/min | 7.03 MiB/min |
high |
80 KiB | 9.38 MiB/min | 14.06 MiB/min |
The table assumes every frame is saved and reaches the per-frame size limit. It is an approximate full-frame image upper limit, not a typical consumption value, total upload limit, or scroll quota. For example, the theoretical image limit for medium at 3 FPS is 40960 × 3 × 60 ÷ 1048576 ≈ 7.03 MiB/min; actual images are usually smaller than 40 KiB.
Sample Estimate Ranges for Six Configurations¶
The following table uses 21 existing Battle images re-encoded with libwebp 1.6.0 according to the Android policy, estimating by multiplying the minimum and maximum image sizes by 120 or 180 images per minute. The lower values represent the reference lower bound for this set of dynamic samples and are not the minimum traffic for all scenarios.
| Preset | Re-encoded Sample Size | 2 FPS Image Estimate | 3 FPS Image Estimate |
|---|---|---|---|
low |
5,482–6,054 bytes/image | 0.63–0.69 MiB/min | 0.94–1.04 MiB/min |
medium |
10,168–11,282 bytes/image | 1.16–1.29 MiB/min | 1.75–1.94 MiB/min |
high |
10,776–12,100 bytes/image | 1.23–1.38 MiB/min | 1.85–2.08 MiB/min |
These are second-pass encoding estimates of already compressed images and have not yet been continuously recorded on real devices for each of the six configurations. low is scaled to 480×270; medium/high use 720×405 source images without upscaling, so the high data does not represent the traffic of 960 px frame capture. This table is not applicable to inferring iOS JPEG traffic and is not a guaranteed range for other scenarios.
Android On-Device Reference¶
On September 13, 2026, the Battle scene of the Creator 2.4.9 Android example from the Guance Cocos development package was captured using captureFps: 3, quality: 'medium', 720 px long edge, and a 40 KiB per-frame limit. All 83 WebP images from this scene were obtained:
| Metric | Result |
|---|---|
| Image size | 720×405 |
| Total image size / span from first to last image | 1,008,030 bytes / 28.516 seconds |
| Per-image size / average size | 11,280–13,098 bytes / 12,144.94 bytes |
| Measured image traffic | 2.02 MiB/min |
| Average frame rate including the initial 1.017-second interval | 2.88 FPS |
| Frame rate in the subsequent stable phase | 2.95 FPS |
| Scaled to full 3 FPS using sample min/max sizes | 1.94–2.25 MiB/min |
| Scaled to full 3 FPS using the average size | 2.08 MiB/min |
This measurement is a device reference from the Guance development package and does not represent a separately completed TrueWatch on-device traffic test. For this Android Battle scene, approximately 2.0–2.1 MiB/min can be used as a reference for image capacity planning. This on-device result is slightly higher than the second-pass encoding estimate for medium in the previous table; on-device data should be preferred. low/high still only have sample re-encoding estimates and cannot be claimed as verified on real devices.
The raw recording of the full playback spans 68.953 seconds, while the console session header displays 48.15 seconds; calculation should use the corresponding raw recording duration. Two images outside the Battle scene could not be retrieved from the server, so the total download amount for the full playback is not a reference for complete upload traffic. Missing images, protocol overhead, and retries cannot be derived from the image estimates above.
Session Sampling and Overall Capacity¶
When frames are identical or approximately static, deduplication reduces the number of images; capture and encoding time can also lower the actual frame rate. Production capacity should be estimated based on the average image size and save frequency on target devices and real scenarios, measuring high-motion scenes, menus, and static pages separately.
Session sampling controls the number of sessions participating in recording and does not change the image limits of an individual recorded session. If sessions are sampled uniformly and recording durations and scene distributions are similar, the ordinary sampled portion can be roughly estimated:
Overall image traffic ≈ total session minutes × sampleRate × average image traffic per minute of recorded sessions
Also combine RUM sampling and the actual minutes that participated in recording. Recording, Segments, and network overhead generated by error-session additional sampling are counted separately. When accurate billing or network capacity data is required, use device-side upload statistics as the source of truth.
Upgrade to alpha.7¶
Upgrade both the base package and the Replay package in the Cocos project root and update the native integration:
npm install @truewatchtech/cocos-sdk@0.1.0-alpha.7 @truewatchtech/cocos-session-replay@0.1.0-alpha.7
npx --no-install truewatch-cocos install --project . --replay
- Projects already using the
alpha.6split-package API continue to reuse the instance returned bywithSessionReplay(baseSdk). - Remove
imagePolicy.maxBytesPerMinuteandimagePolicy.adaptiveCapture. UsecaptureFpsto control frequency, and usemaxImageDimensionandimagePolicy.maxFrameBytesto control per-image size. Keeping these removed fields in old JavaScript configuration does not enable the per-minute budget. - Every image is subject to the per-frame limit, including the first frames of new Views and screen orientation changes. The scroll budget-based frame rate reduction and the separate first-frame burst allowance are no longer available. Re-evaluate actual traffic; see Traffic Calculation.
- Reopen Creator, then generate and compile the native project; run
pod installagain when using CocoaPods. Replacing JavaScript only does not update the native image processing logic. - Verify the Creator 3 display, button and joystick response, Replay touch positions, and check View switching, landscape/portrait changes, and privacy masks.
Migrating from the Integrated Package¶
When upgrading from 0.1.0-alpha.5 or earlier:
- Upgrade the base package to the released
0.1.0-alpha.6or later, install the Replay package of the same version, and re-run the installer with--replay. - Call
withSessionReplay(baseSdk)before the firststart()/attach(), so business code reuses the combined instance. - Change the previously independently exported
setReplayCamera(camera)totruewatchSdk.setReplayCamera(camera). - Move the imports of Replay types such as
FTSessionReplayConfig,FTHybridSessionReplayConfig, andFTReplayImagePolicyto@truewatchtech/cocos-session-replay/creator2or/creator3. For complete configurations containing Replay, useFTCocosReplayConfigandFTCocosHybridReplayConfigrespectively. - Regenerate and compile the native project; replacing JavaScript only does not install the new Replay Bridge.
If Replay is no longer used, remove the composition and Replay configuration, run the installer with --no-replay, and rebuild. This switch removes the native integration managed by the installer. For pausing frame capture at runtime, still use replay.stop() below; use leaveCocos() in Hybrid scenarios.
Start and Stop¶
With the Replay package installed and composed, if replay was not passed in truewatchSdk.start(), Session Replay can also be started separately after RUM initialization and after a View has started:
truewatchSdk.replay.start({
sampleRate: 1,
captureFps: 1,
maxImageDimension: 720,
touchPrivacy: 'show',
});
Stop frame capture:
truewatchSdk.shutdown() also stops frame capture. Do not start Session Replay twice by simultaneously using truewatchSdk.start({ replay: ... }) and truewatchSdk.replay.start().
Hybrid mode with a native host does not use the start and stop interfaces above. Session Replay is initialized by the native host in native recorder mode, and Cocos automatically switches to the external capture source between enterCocos() and leaveCocos(). See Native Host Hybrid Access.
Performance Recommendations¶
- Start verification with
captureFps: 1andimagePolicy: { quality: 'medium' }; increase to2 fpsonly when a more continuous picture is needed. - Enable sampling only in business environments where replay is required.
- On low-end devices with GPU, memory, or disk pressure, first switch to
low, then reduce the longest-edge dimension and the sampling rate. - Verify image orientation and mask positions in landscape/portrait switching, multi-Camera, complex UI, and low-frame-rate scenarios.