@streaming-cdn/rtc-web 1.3.24 → 1.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # RTC Web SDK 1.3.24
1
+ # RTC Web SDK 1.3.25
2
2
 
3
3
  The customer package contains compiled ESM/UMD JavaScript and TypeScript declarations. It intentionally excludes implementation source and source maps. Example source remains available under `examples/`.
4
4
 
@@ -357,6 +357,16 @@ operational diagnostics. Tokens, SDP, media, and chat content are never part of
357
357
  that report. Set `reportSignalingDiagnostics: false` only when replacing this
358
358
  with an application-owned diagnostics pipeline.
359
359
 
360
+ ## 1.3.25 notes
361
+
362
+ - **B21 — per-peer quality no longer ratchets down on bandwidth-pinned
363
+ links.** The step-up gate required `qualityLimitationReason` to clear; on
364
+ links that pin it at `bandwidth`, upgrades were mathematically impossible.
365
+ Both directions now share `bandwidthSustainRatio` (default 0.7): down
366
+ judges the current tier's target, up judges the next tier's, so promotions
367
+ cannot flap. `bandwidthHeadroomRatio` is retired and ignored, and
368
+ `goodSamplesToStepUp` is effective again in this state.
369
+
360
370
  ## 1.3.24 notes
361
371
 
362
372
  - `callupdated` now carries `actorExternalId`, matching the React Native SDK.
@@ -0,0 +1,82 @@
1
+ import { type ZipEntry } from "./zip-reader";
2
+ /**
3
+ * Custom avatar model library.
4
+ *
5
+ * A browser cannot look inside the user's Downloads folder — no web API
6
+ * enumerates the file system. So a model obtained from a third-party site is
7
+ * handed over once, explicitly (file picker or drag-and-drop), and kept here so
8
+ * every later call finds it already imported.
9
+ *
10
+ * Models never leave the device: the avatar is rendered locally into the
11
+ * outgoing video track, so remote participants receive video, never the model
12
+ * file. Nothing here uploads, and nothing here depends on three.js — reading a
13
+ * VRM's own metadata only needs its glTF JSON chunk.
14
+ */
15
+ export interface AvatarModelLicense {
16
+ /** Character name declared by the model, when present. */
17
+ title: string | null;
18
+ author: string | null;
19
+ /** Who the model may be used by, verbatim from the file. */
20
+ allowedUser: string | null;
21
+ /** Whether the author permits commercial use, verbatim from the file. */
22
+ commercialUse: string | null;
23
+ licenseName: string | null;
24
+ licenseUrl: string | null;
25
+ }
26
+ export interface AvatarModelEntry {
27
+ id: string;
28
+ /** Display name: the model's own title when it declares one, else the file name. */
29
+ name: string;
30
+ bytes: number;
31
+ importedAt: string;
32
+ /** The author's terms as declared inside the file; null when it declares none. */
33
+ license: AvatarModelLicense | null;
34
+ }
35
+ export interface AvatarModelImportOptions {
36
+ /** Overrides the name taken from the model metadata or file name. */
37
+ name?: string;
38
+ /** Rejects models above this size. Defaults to AVATAR_MODEL_MAX_BYTES. */
39
+ maxBytes?: number;
40
+ /**
41
+ * Which character to take when the source is an archive holding several.
42
+ * Comes from AvatarModelError.choices on a previous "archive_many_models".
43
+ */
44
+ entryPath?: string;
45
+ }
46
+ /**
47
+ * Lists the characters inside a downloaded pack, so a caller can offer a
48
+ * choice before importing. Returns an empty list for a bare .vrm.
49
+ */
50
+ export declare function listArchiveModels(source: File | ArrayBuffer): Promise<string[]>;
51
+ /** Well past any sensible VRM character, far below anything that would wedge a tab. */
52
+ export declare const AVATAR_MODEL_MAX_BYTES: number;
53
+ export type AvatarModelErrorCode = "model_too_large" | "model_not_glb" | "model_not_vrm" | "model_unreadable" | "storage_unavailable" | "archive_no_model" | "archive_many_models" | "archive_unreadable" | "archive_encrypted" | "archive_unsupported";
54
+ export declare class AvatarModelError extends Error {
55
+ readonly code: AvatarModelErrorCode;
56
+ /**
57
+ * The characters found inside an archive. Set only for
58
+ * "archive_many_models", so the caller can ask which one to import and pass
59
+ * it back as importAvatarModel's entryPath.
60
+ */
61
+ readonly choices: string[];
62
+ constructor(code: AvatarModelErrorCode, message: string, choices?: string[]);
63
+ }
64
+ /** Entries a character pack holds that are actually characters. */
65
+ export declare function findArchiveModels(entries: ZipEntry[]): ZipEntry[];
66
+ /**
67
+ * Reads the VRM metadata a model declares about itself. Supports VRM 0.x
68
+ * (extensions.VRM.meta) and VRM 1.0 (extensions.VRMC_vrm.meta), whose field
69
+ * names differ. Returns null for a glTF that carries neither.
70
+ */
71
+ export declare function readAvatarModelMetadata(buffer: ArrayBuffer): AvatarModelLicense | null;
72
+ /**
73
+ * Validates a model the user supplied and keeps it for later calls. The bytes
74
+ * are checked before anything renders them: an oversized or non-VRM file is
75
+ * rejected with a code the UI can explain, never handed to the renderer.
76
+ */
77
+ export declare function importAvatarModel(source: File | ArrayBuffer, options?: AvatarModelImportOptions): Promise<AvatarModelEntry>;
78
+ /** Every model this device has imported, newest first. */
79
+ export declare function listAvatarModels(): Promise<AvatarModelEntry[]>;
80
+ /** The stored bytes for a model, or null when it is no longer present. */
81
+ export declare function readAvatarModel(id: string): Promise<ArrayBuffer | null>;
82
+ export declare function removeAvatarModel(id: string): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import type { AvatarRig } from "./avatar-rig";
2
+ import type { AvatarRenderer } from "./avatar";
3
+ export interface VrmRenderOptions {
4
+ /** Multiplier on head joint rotation; 1 mirrors the person exactly. */
5
+ headGain?: number;
6
+ }
7
+ export interface VrmStage {
8
+ canvas: HTMLCanvasElement;
9
+ applyAndRender(rig: AvatarRig, options?: VrmRenderOptions): void;
10
+ dispose(): void;
11
+ }
12
+ /**
13
+ * Adapts a VrmStage to the SDK's pluggable `AvatarRenderer`, so
14
+ * `createAvatarPipeline` can publish a VRM character instead of the built-in
15
+ * stylized avatar. In overlay mode the bust is framed around the head, so it
16
+ * spans a few head radii over the tracked anchor.
17
+ */
18
+ export declare function vrmAvatarRenderer(stage: VrmStage, options?: {
19
+ headGain?: number;
20
+ }): AvatarRenderer;
21
+ export declare function createVrmStage(buffer: ArrayBuffer, size?: number): Promise<VrmStage>;
22
+ /**
23
+ * One-call helper: bytes in, pipeline renderer out. Pair it with
24
+ * readAvatarModel() from the model library to render a character the user
25
+ * imported from a third-party site.
26
+ */
27
+ export declare function createVrmAvatarRenderer(buffer: ArrayBuffer, options?: {
28
+ size?: number;
29
+ headGain?: number;
30
+ }): Promise<AvatarRenderer>;
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export type { BackgroundPreset, OverlayEffect, Segmenter, VideoEffectsOptions, V
9
9
  export { AVATAR_TUNING_DEFAULTS, createAvatarPipeline, stylizedAvatarRenderer } from "./avatar";
10
10
  export type { AvatarMode, AvatarPipeline, AvatarPipelineOptions, AvatarRenderer, AvatarTuning, FaceTracker, FaceTrackerResult } from "./avatar";
11
11
  export { drawAvatar, headPoseFromMatrix, neutralRig, rigFromBlendshapes, smoothRig } from "./avatar-rig";
12
+ export { AVATAR_MODEL_MAX_BYTES, AvatarModelError, importAvatarModel, listArchiveModels, listAvatarModels, readAvatarModel, readAvatarModelMetadata, removeAvatarModel } from "./avatar-library";
13
+ export type { AvatarModelEntry, AvatarModelErrorCode, AvatarModelImportOptions, AvatarModelLicense } from "./avatar-library";
12
14
  export type { AvatarAnchor, AvatarRig, BlendshapeCategory } from "./avatar-rig";
13
15
  export type RtcMode = "voice" | "video" | "meeting";
14
16
  /**
@@ -393,4 +395,4 @@ export declare class RtcIncomingClient extends EventTarget {
393
395
  private emit;
394
396
  }
395
397
  export declare function createRtcIncomingClient(options: RtcIncomingClientOptions): RtcIncomingClient;
396
- export declare const version = "1.3.24";
398
+ export declare const version = "1.3.25";
@@ -37,8 +37,20 @@ export interface PeerQualityThresholds {
37
37
  goodLossPct: number;
38
38
  goodRttMs: number;
39
39
  sustainRatio: number;
40
- /** "bandwidth" counts only when the uplink estimate is below this multiple of the tier target. */
41
- bandwidthHeadroomRatio: number;
40
+ /**
41
+ * One ratio governs both directions of the bandwidth-limited path (B21).
42
+ * Down: "bandwidth" counts as congestion only below this fraction of the
43
+ * CURRENT tier's target. Up: a bandwidth-limited but otherwise clean sample
44
+ * counts as good once the uplink covers this fraction of the NEXT tier's
45
+ * target — a capped encoder never lets estimation probe much past its send
46
+ * rate, so demanding the full next-tier target keeps the gate shut exactly
47
+ * when the cap is what throttles discovery. The default (0.7) exceeds every
48
+ * adjacent-tier cap ratio, so a promotion never yields fewer usable bits
49
+ * than the tier being left was allowed at full cap.
50
+ */
51
+ bandwidthSustainRatio: number;
52
+ /** @deprecated No longer used; superseded by bandwidthSustainRatio (B21). */
53
+ bandwidthHeadroomRatio?: number;
42
54
  }
43
55
  export declare const defaultPeerQualityThresholds: PeerQualityThresholds;
44
56
  export interface PeerQualitySample {
@@ -0,0 +1,61 @@
1
+ import * as l from "three";
2
+ import { GLTFLoader as L } from "three/examples/jsm/loaders/GLTFLoader.js";
3
+ import { VRMLoaderPlugin as A, VRMUtils as k } from "@pixiv/three-vrm";
4
+ function M(t, c) {
5
+ return {
6
+ render(n, p, s, m, o) {
7
+ if (t.applyAndRender(p, c), o) {
8
+ const a = o.radius * 5.2;
9
+ n.drawImage(t.canvas, o.x - a / 2, o.y - a * 0.42, a, a);
10
+ } else {
11
+ const a = Math.min(s, m);
12
+ n.drawImage(t.canvas, (s - a) / 2, (m - a) / 2, a, a);
13
+ }
14
+ },
15
+ dispose() {
16
+ t.dispose();
17
+ }
18
+ };
19
+ }
20
+ async function g(t, c = 512) {
21
+ const n = document.createElement("canvas");
22
+ n.width = c, n.height = c;
23
+ const p = new l.WebGLRenderer({ canvas: n, alpha: !0, antialias: !0 });
24
+ p.setClearColor(0, 0);
25
+ const s = new l.Scene();
26
+ s.add(new l.AmbientLight(16777215, 1.1));
27
+ const m = new l.DirectionalLight(16777215, 1.4);
28
+ m.position.set(0.5, 1.2, 1.5), s.add(m);
29
+ const o = new l.PerspectiveCamera(28, 1, 0.1, 20), a = new L();
30
+ a.register((e) => new A(e));
31
+ const w = await a.parseAsync(t, ""), i = w.userData.vrm;
32
+ k.removeUnnecessaryVertices(w.scene), k.combineSkeletons(w.scene), k.rotateVRM0(i), s.add(i.scene);
33
+ const u = i.humanoid?.getNormalizedBoneNode("head"), h = new l.Vector3(0, 1.35, 0);
34
+ u && u.getWorldPosition(h), o.position.set(0, h.y + 0.03, 0.85), o.lookAt(0, h.y + 0.01, 0);
35
+ const f = new l.Object3D();
36
+ f.position.set(0, h.y, 2), s.add(f), i.lookAt && (i.lookAt.target = f);
37
+ const b = new l.Clock();
38
+ return {
39
+ canvas: n,
40
+ applyAndRender(e, R) {
41
+ const y = R?.headGain ?? 0.9, r = i.expressionManager;
42
+ if (r) {
43
+ const V = r.expressionMap ?? {}, d = (v) => !!V[v];
44
+ d("blinkLeft") || d("blinkRight") ? (r.setValue("blinkLeft", e.blinkLeft), r.setValue("blinkRight", e.blinkRight)) : d("blink") && r.setValue("blink", (e.blinkLeft + e.blinkRight) / 2), d("aa") && r.setValue("aa", Math.max(0, e.jawOpen)), d("happy") && r.setValue("happy", Math.max(0, e.smile)), d("angry") && r.setValue("angry", Math.max(0, -e.smile) * 0.6);
45
+ }
46
+ u && u.rotation.set(-e.headPitch * y, e.headYaw * y, -e.headRoll * y), f.position.set(e.pupilX * 1.2, h.y - e.pupilY * 0.8, 2), i.update(b.getDelta()), p.render(s, o);
47
+ },
48
+ dispose() {
49
+ k.deepDispose(i.scene), p.dispose();
50
+ }
51
+ };
52
+ }
53
+ async function D(t, c = {}) {
54
+ const n = await g(t, c.size ?? 640);
55
+ return M(n, { headGain: c.headGain });
56
+ }
57
+ export {
58
+ D as createVrmAvatarRenderer,
59
+ g as createVrmStage,
60
+ M as vrmAvatarRenderer
61
+ };
@@ -0,0 +1 @@
1
+ (function(s,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("three"),require("three/examples/jsm/loaders/GLTFLoader.js"),require("@pixiv/three-vrm")):typeof define=="function"&&define.amd?define(["exports","three","three/examples/jsm/loaders/GLTFLoader.js","@pixiv/three-vrm"],f):(s=typeof globalThis<"u"?globalThis:s||self,f(s.StreamingCdnRtcAvatarVrm={},s.THREE,s.THREE,s.THREE_VRM))})(this,(function(s,f,M,m){"use strict";function L(e){const i=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const t in e)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(i,t,r.get?r:{enumerable:!0,get:()=>e[t]})}}return i.default=e,Object.freeze(i)}const o=L(f);function w(e,i){return{render(t,r,d,h,c){if(e.applyAndRender(r,i),c){const a=c.radius*5.2;t.drawImage(e.canvas,c.x-a/2,c.y-a*.42,a,a)}else{const a=Math.min(d,h);t.drawImage(e.canvas,(d-a)/2,(h-a)/2,a,a)}},dispose(){e.dispose()}}}async function V(e,i=512){const t=document.createElement("canvas");t.width=i,t.height=i;const r=new o.WebGLRenderer({canvas:t,alpha:!0,antialias:!0});r.setClearColor(0,0);const d=new o.Scene;d.add(new o.AmbientLight(16777215,1.1));const h=new o.DirectionalLight(16777215,1.4);h.position.set(.5,1.2,1.5),d.add(h);const c=new o.PerspectiveCamera(28,1,.1,20),a=new M.GLTFLoader;a.register(n=>new m.VRMLoaderPlugin(n));const b=await a.parseAsync(e,""),l=b.userData.vrm;m.VRMUtils.removeUnnecessaryVertices(b.scene),m.VRMUtils.combineSkeletons(b.scene),m.VRMUtils.rotateVRM0(l),d.add(l.scene);const y=l.humanoid?.getNormalizedBoneNode("head"),R=new o.Vector3(0,1.35,0);y&&y.getWorldPosition(R),c.position.set(0,R.y+.03,.85),c.lookAt(0,R.y+.01,0);const v=new o.Object3D;v.position.set(0,R.y,2),d.add(v),l.lookAt&&(l.lookAt.target=v);const j=new o.Clock;return{canvas:t,applyAndRender(n,A){const k=A?.headGain??.9,p=l.expressionManager;if(p){const T=p.expressionMap??{},u=x=>!!T[x];u("blinkLeft")||u("blinkRight")?(p.setValue("blinkLeft",n.blinkLeft),p.setValue("blinkRight",n.blinkRight)):u("blink")&&p.setValue("blink",(n.blinkLeft+n.blinkRight)/2),u("aa")&&p.setValue("aa",Math.max(0,n.jawOpen)),u("happy")&&p.setValue("happy",Math.max(0,n.smile)),u("angry")&&p.setValue("angry",Math.max(0,-n.smile)*.6)}y&&y.rotation.set(-n.headPitch*k,n.headYaw*k,-n.headRoll*k),v.position.set(n.pupilX*1.2,R.y-n.pupilY*.8,2),l.update(j.getDelta()),r.render(d,c)},dispose(){m.VRMUtils.deepDispose(l.scene),r.dispose()}}}async function g(e,i={}){const t=await V(e,i.size??640);return w(t,{headGain:i.headGain})}s.createVrmAvatarRenderer=g,s.createVrmStage=V,s.vrmAvatarRenderer=w,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));