diff --git a/src/app/App.tsx b/src/app/App.tsx index 967f5613..a71ec8ab 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -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"; } } diff --git a/src/runner-web/RunnerCanvas.tsx b/src/runner-web/RunnerCanvas.tsx index ff7183f3..8e1c5ff4 100644 --- a/src/runner-web/RunnerCanvas.tsx +++ b/src/runner-web/RunnerCanvas.tsx @@ -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; loadedModelAssets: Record; loadedImageAssets: Record; + loadedAudioAssets: Record; 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);