Add audio asset support in App and RunnerCanvas components

This commit is contained in:
2026-04-02 19:40:11 +02:00
parent 12facde036
commit 21c0c0a5fc
2 changed files with 35 additions and 3 deletions

View File

@@ -9,6 +9,7 @@ import {
} from "react";
import { createCreateBoxBrushCommand } from "../commands/create-box-brush-command";
import { createImportAudioAssetCommand } from "../commands/import-audio-asset-command";
import { createImportBackgroundImageAssetCommand } from "../commands/import-background-image-asset-command";
import { createImportModelAssetCommand } from "../commands/import-model-asset-command";
import { createMoveBoxBrushCommand } from "../commands/move-box-brush-command";
@@ -44,6 +45,11 @@ import {
getModelInstanceDisplayLabelById,
getSortedModelInstanceDisplayLabels
} from "../assets/model-instance-labels";
import {
importAudioAssetFromFile,
loadAudioAssetFromStorage,
type LoadedAudioAsset
} from "../assets/audio-assets";
import {
importModelAssetFromFile,
importModelAssetFromFiles,
@@ -59,7 +65,7 @@ import {
type ImportedImageAssetResult,
type LoadedImageAsset
} from "../assets/image-assets";
import type { ImageAssetRecord, ModelAssetRecord, ProjectAssetRecord } from "../assets/project-assets";
import type { AudioAssetRecord, ImageAssetRecord, ModelAssetRecord, ProjectAssetRecord } from "../assets/project-assets";
import { getProjectAssetKindLabel } from "../assets/project-assets";
import {
BOX_FACE_IDS,
@@ -85,7 +91,11 @@ import {
DEFAULT_POINT_LIGHT_DISTANCE,
DEFAULT_POINT_LIGHT_INTENSITY,
DEFAULT_SOUND_EMITTER_GAIN,
DEFAULT_SOUND_EMITTER_AUDIO_ASSET_ID,
DEFAULT_SOUND_EMITTER_RADIUS,
DEFAULT_SOUND_EMITTER_VOLUME,
DEFAULT_SOUND_EMITTER_REF_DISTANCE,
DEFAULT_SOUND_EMITTER_MAX_DISTANCE,
DEFAULT_TELEPORT_TARGET_YAW_DEGREES,
DEFAULT_SPOT_LIGHT_ANGLE_DEGREES,
DEFAULT_SPOT_LIGHT_COLOR_HEX,
@@ -333,6 +343,10 @@ function isImageAsset(asset: ProjectAssetRecord): asset is ImageAssetRecord {
return asset.kind === "image";
}
function isAudioAsset(asset: ProjectAssetRecord): asset is AudioAssetRecord {
return asset.kind === "audio";
}
function formatByteLength(byteLength: number): string {
if (byteLength < 1024) {
return `${byteLength} B`;
@@ -383,6 +397,17 @@ function formatImageAssetSummary(asset: ImageAssetRecord): string {
return details.join(" | ");
}
function formatAudioAssetSummary(asset: AudioAssetRecord): string {
const details = [
asset.metadata.durationSeconds === null ? "duration unavailable" : `${asset.metadata.durationSeconds.toFixed(2)}s`,
asset.metadata.channelCount === null ? "channels unavailable" : `${asset.metadata.channelCount} channel${asset.metadata.channelCount === 1 ? "" : "s"}`,
asset.metadata.sampleRateHz === null ? "sample rate unavailable" : `${asset.metadata.sampleRateHz} Hz`,
formatByteLength(asset.byteLength)
];
return details.join(" | ");
}
function createModelInstancePlacementPosition(asset: ModelAssetRecord, anchor: Vec3 | null): Vec3 {
const boundingBox = asset.metadata.boundingBox;
@@ -466,6 +491,10 @@ function getInteractionActionLabel(link: InteractionLink): string {
return "Play Animation";
case "stopAnimation":
return "Stop Animation";
case "playSound":
return "Play Sound";
case "stopSound":
return "Stop Sound";
}
}

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from "react";
import type { LoadedAudioAsset } from "../assets/audio-assets";
import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { LoadedImageAsset } from "../assets/image-assets";
import type { ProjectAssetRecord } from "../assets/project-assets";
@@ -14,6 +15,7 @@ interface RunnerCanvasProps {
projectAssets: Record<string, ProjectAssetRecord>;
loadedModelAssets: Record<string, LoadedModelAsset>;
loadedImageAssets: Record<string, LoadedImageAsset>;
loadedAudioAssets: Record<string, LoadedAudioAsset>;
navigationMode: RuntimeNavigationMode;
onRuntimeMessageChange(message: string | null): void;
onFirstPersonTelemetryChange(telemetry: FirstPersonTelemetry | null): void;
@@ -25,6 +27,7 @@ export function RunnerCanvas({
projectAssets,
loadedModelAssets,
loadedImageAssets,
loadedAudioAssets,
navigationMode,
onRuntimeMessageChange,
onFirstPersonTelemetryChange,
@@ -78,8 +81,8 @@ export function RunnerCanvas({
}, [onFirstPersonTelemetryChange, onInteractionPromptChange, onRuntimeMessageChange]);
useEffect(() => {
hostRef.current?.updateAssets(projectAssets, loadedModelAssets, loadedImageAssets);
}, [projectAssets, loadedModelAssets, loadedImageAssets]);
hostRef.current?.updateAssets(projectAssets, loadedModelAssets, loadedImageAssets, loadedAudioAssets);
}, [projectAssets, loadedModelAssets, loadedImageAssets, loadedAudioAssets]);
useEffect(() => {
hostRef.current?.loadScene(runtimeScene);