@kubohiroya/turbowarp-teachable-machine 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tm.js CHANGED
@@ -553,7 +553,7 @@
553
553
  }
554
554
  var package_default = {
555
555
  name: "@kubohiroya/turbowarp-teachable-machine",
556
- version: "3.3.0",
556
+ version: "3.4.0",
557
557
  description: "A TurboWarp extension for recognition using Teachable Machine pose, image, and audio models.",
558
558
  author: "Hiroya Kubo <hiroya@cuc.ac.jp>",
559
559
  license: "MPL-2.0",
@@ -621,6 +621,7 @@
621
621
  "prepack": "npm run build && npm run release:consistency:check"
622
622
  },
623
623
  devDependencies: {
624
+ "@kubohiroya/turbowarp-camera-source": "0.13.0",
624
625
  "@kubohiroya/vite-plugin-turbowarp-extension": "0.4.0",
625
626
  "@teachablemachine/image": "0.8.5",
626
627
  "@teachablemachine/pose": "0.8.6",
@@ -647,6 +648,23 @@
647
648
  };
648
649
  //#endregion
649
650
  //#region src/extension.ts
651
+ /**
652
+ * Where Camera Source puts itself on the VM runtime.
653
+ *
654
+ * Declared here rather than imported as a value, because this file is bundled into a single-file
655
+ * extension and no dependency's code has ever been inlined into it. The key and the member read
656
+ * below are pinned against Camera Source's own published constant and narrowing function in the
657
+ * tests, which import both and are not shipped, so a rename upstream fails here rather than
658
+ * silently reading an absent extension as "not loaded".
659
+ */
660
+ var CAMERA_SOURCE_RUNTIME_KEY = "ext_kubohiroyacamerasource";
661
+ function readCameraSource(runtime) {
662
+ if (typeof runtime !== "object" || runtime === null) return void 0;
663
+ const candidate = runtime[CAMERA_SOURCE_RUNTIME_KEY];
664
+ if (typeof candidate !== "object" || candidate === null) return void 0;
665
+ const { acquireCamera } = candidate;
666
+ return typeof acquireCamera === "function" ? candidate : void 0;
667
+ }
650
668
  var EXTENSION_ID = "kubohiroyatm";
651
669
  var VERSION = `${package_default.version}-typescript`;
652
670
  var DOCS_URI = "https://kubohiroya.github.io/turbowarp-teachable-machine/";
@@ -929,6 +947,7 @@
929
947
  this.modelURL = "";
930
948
  this.model = null;
931
949
  this.webcam = null;
950
+ this.cameraLease = null;
932
951
  this.cameraRunning = false;
933
952
  this.cameraSelection = "default";
934
953
  this.cameraSelectionIsDeviceId = false;
@@ -1150,8 +1169,11 @@
1150
1169
  await this.ensureComputeBackend();
1151
1170
  }
1152
1171
  cleanupCameraResources() {
1172
+ const lease = this.cameraLease;
1173
+ this.cameraLease = null;
1153
1174
  const video = this.webcam?.webcam;
1154
- if (video?.srcObject) {
1175
+ if (lease) lease.release();
1176
+ else if (video?.srcObject) {
1155
1177
  video.srcObject.getTracks().forEach((track) => track.stop());
1156
1178
  video.srcObject = null;
1157
1179
  }
@@ -1184,11 +1206,16 @@
1184
1206
  if (!runtime) throw new Error("Teachable Machine: Runtime is unavailable.");
1185
1207
  if (!("Webcam" in runtime)) throw new Error("Teachable Machine: Camera runtime is unavailable in the current mode.");
1186
1208
  this.webcam = new runtime.Webcam(320, 240, true);
1187
- const constraints = cameraConstraints(this.resolvedCameraSelection());
1209
+ const lease = await this.acquireSharedCamera();
1210
+ if (lease) {
1211
+ this.cameraLease = lease;
1212
+ this.webcam.webcam = lease.getFrameSource().element;
1213
+ }
1214
+ const constraints = lease ? void 0 : cameraConstraints(this.resolvedCameraSelection());
1188
1215
  if (constraints) await this.webcam.setup(constraints);
1189
1216
  else await this.webcam.setup();
1190
1217
  initializeCameraReadbackContext(this.webcam.canvas);
1191
- await this.webcam.play();
1218
+ if (!lease) await this.webcam.play();
1192
1219
  this.attachPreviewToStage();
1193
1220
  this.cameraRunning = true;
1194
1221
  try {
@@ -1203,6 +1230,29 @@
1203
1230
  throw error;
1204
1231
  }
1205
1232
  }
1233
+ /**
1234
+ * A lease on the shared camera, or null when Camera Source is not loaded.
1235
+ *
1236
+ * Camera Source owns the device and decides when a stream stops, which is what lets one camera
1237
+ * serve pose recognition and anything else reading frames at the same time. It is a separate
1238
+ * TurboWarp extension, and this one is also distributed for standalone URL loading, so its
1239
+ * absence is ordinary rather than a failure: the self-acquired path stays for it.
1240
+ *
1241
+ * One camera id for every visual recognition mode. Opening a second device for a work that
1242
+ * recognises both poses and images is not what sharing is for, and `pose` is the role name
1243
+ * Camera Source's own documentation uses for this consumer.
1244
+ */
1245
+ async acquireSharedCamera() {
1246
+ const cameraSource = readCameraSource(Scratch.vm?.runtime);
1247
+ if (!cameraSource) return null;
1248
+ const selection = this.resolvedCameraSelection();
1249
+ const options = {
1250
+ owner: package_default.name,
1251
+ cameraId: "pose"
1252
+ };
1253
+ if (selection.kind === "device") options.deviceId = selection.value;
1254
+ return cameraSource.acquireCamera(options);
1255
+ }
1206
1256
  stopCamera() {
1207
1257
  try {
1208
1258
  this.stopRecognition();
@@ -152,6 +152,19 @@ export declare class TMExtension {
152
152
  ensureLibrariesLoaded(): Promise<void>;
153
153
  cleanupCameraResources(): void;
154
154
  startCamera(): Promise<void>;
155
+ /**
156
+ * A lease on the shared camera, or null when Camera Source is not loaded.
157
+ *
158
+ * Camera Source owns the device and decides when a stream stops, which is what lets one camera
159
+ * serve pose recognition and anything else reading frames at the same time. It is a separate
160
+ * TurboWarp extension, and this one is also distributed for standalone URL loading, so its
161
+ * absence is ordinary rather than a failure: the self-acquired path stays for it.
162
+ *
163
+ * One camera id for every visual recognition mode. Opening a second device for a work that
164
+ * recognises both poses and images is not what sharing is for, and `pose` is the role name
165
+ * Camera Source's own documentation uses for this consumer.
166
+ */
167
+ private acquireSharedCamera;
155
168
  stopCamera(): void;
156
169
  isCameraRunning(): any;
157
170
  getCameraMenuItems(): {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-teachable-machine",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "A TurboWarp extension for recognition using Teachable Machine pose, image, and audio models.",
5
5
  "author": "Hiroya Kubo <hiroya@cuc.ac.jp>",
6
6
  "license": "MPL-2.0",
@@ -72,6 +72,7 @@
72
72
  "prepack": "npm run build && npm run release:consistency:check"
73
73
  },
74
74
  "devDependencies": {
75
+ "@kubohiroya/turbowarp-camera-source": "0.13.0",
75
76
  "@kubohiroya/vite-plugin-turbowarp-extension": "0.4.0",
76
77
  "@teachablemachine/image": "0.8.5",
77
78
  "@teachablemachine/pose": "0.8.6",