diff --git a/src/bit-components.js b/src/bit-components.js
index c71bfb4627..07ab035b78 100644
--- a/src/bit-components.js
+++ b/src/bit-components.js
@@ -281,6 +281,8 @@ export const VideoMenu = defineComponent({
playIndicatorRef: Types.eid,
pauseIndicatorRef: Types.eid,
snapRef: Types.eid,
+ volUpRef: Types.eid,
+ volDownRef: Types.eid,
clearTargetTimer: Types.f64
});
export const AudioEmitter = defineComponent({
diff --git a/src/bit-systems/video-menu-system.ts b/src/bit-systems/video-menu-system.ts
index 2224de0822..9939d73989 100644
--- a/src/bit-systems/video-menu-system.ts
+++ b/src/bit-systems/video-menu-system.ts
@@ -1,9 +1,10 @@
import { addComponent, defineQuery, entityExists, hasComponent, removeComponent } from "bitecs";
-import { Object3D, Plane, Ray, Vector3 } from "three";
+import { MathUtils, Object3D, Plane, Ray, Vector3 } from "three";
import { clamp, mapLinear } from "three/src/math/MathUtils";
import { Text as TroikaText } from "troika-three-text";
import { HubsWorld } from "../app";
import {
+ AudioEmitter,
CursorRaycastable,
Deleting,
EntityStateDirty,
@@ -27,9 +28,11 @@ import { paths } from "../systems/userinput/paths";
import { isFacingCamera } from "../utils/three-utils";
import { Emitter2Audio } from "./audio-emitter-system";
import { EntityID } from "../utils/networking-types";
-import { findAncestorWithComponent, hasAnyComponent } from "../utils/bit-utils";
+import { findAncestorWithComponent, findChildWithComponent, hasAnyComponent } from "../utils/bit-utils";
import { ObjectMenuTransformFlags } from "../inflators/object-menu-transform";
import { MediaType } from "../utils/media-utils";
+import { VolumeControlsMaterial } from "../prefabs/video-menu";
+import { updateAudioSettings } from "../update-audio-settings";
const videoMenuQuery = defineQuery([VideoMenu]);
const hoveredQuery = defineQuery([HoveredRemoteRight]);
@@ -116,6 +119,12 @@ function flushToObject3Ds(world: HubsWorld, menu: EntityID, frozen: boolean) {
ObjectMenuTransform.flags[menu] |= ObjectMenuTransformFlags.Enabled;
const snapButton = world.eid2obj.get(VideoMenu.snapRef[menu])!;
snapButton.visible = MediaInfo.mediaType[target] === MediaType.VIDEO;
+
+ const audioEid = findChildWithComponent(world, AudioEmitter, target)!;
+ if (audioEid) {
+ const audio = APP.audios.get(audioEid)!;
+ VolumeControlsMaterial.uniforms.volume.value = audio.gain.gain.value;
+ }
} else {
obj.removeFromParent();
setCursorRaycastable(world, menu, false);
@@ -127,6 +136,20 @@ function clicked(world: HubsWorld, eid: EntityID) {
return hasComponent(world, Interacted, eid);
}
+const MAX_GAIN_MULTIPLIER = 2;
+function changeVolumeBy(audioEid: EntityID, volume: number) {
+ let gainMultiplier = 1.0;
+ if (APP.gainMultipliers.has(audioEid)) {
+ gainMultiplier = APP.gainMultipliers.get(audioEid)!;
+ }
+ gainMultiplier = MathUtils.clamp(gainMultiplier + volume, 0, MAX_GAIN_MULTIPLIER);
+ APP.gainMultipliers.set(audioEid, gainMultiplier);
+ const audio = APP.audios.get(audioEid);
+ if (audio) {
+ updateAudioSettings(audioEid, audio);
+ }
+}
+
function handleClicks(world: HubsWorld, menu: EntityID) {
const videoEid = VideoMenu.videoRef[menu];
const video = MediaVideoData.get(videoEid)!;
@@ -150,6 +173,16 @@ function handleClicks(world: HubsWorld, menu: EntityID) {
} else if (clicked(world, VideoMenu.snapRef[menu])) {
const video = VideoMenu.videoRef[menu];
addComponent(world, MediaSnapped, video);
+ } else if (clicked(world, VideoMenu.volUpRef[menu])) {
+ const audioEid = findChildWithComponent(world, AudioEmitter, VideoMenu.videoRef[menu])!;
+ if (audioEid) {
+ changeVolumeBy(audioEid, 0.2);
+ }
+ } else if (clicked(world, VideoMenu.volDownRef[menu])) {
+ const audioEid = findChildWithComponent(world, AudioEmitter, VideoMenu.videoRef[menu])!;
+ if (audioEid) {
+ changeVolumeBy(audioEid, -0.2);
+ }
}
}
diff --git a/src/prefabs/video-menu.tsx b/src/prefabs/video-menu.tsx
index 8cecb41e0d..08f38bb639 100644
--- a/src/prefabs/video-menu.tsx
+++ b/src/prefabs/video-menu.tsx
@@ -1,5 +1,5 @@
/** @jsx createElementEntity */
-import { BoxBufferGeometry, Mesh, MeshBasicMaterial, PlaneBufferGeometry } from "three";
+import { BoxBufferGeometry, FrontSide, Mesh, MeshBasicMaterial, PlaneBufferGeometry, ShaderChunk } from "three";
import { Label } from "../prefabs/camera-tool";
import { ArrayVec3, Attrs, createElementEntity, createRef } from "../utils/jsx-entity";
import playImageUrl from "../assets/images/sprites/notice/play.png";
@@ -16,10 +16,36 @@ export async function loadVideoMenuButtonIcons() {
]);
}
+export const VolumeControlsMaterial = new THREE.ShaderMaterial({
+ uniforms: {
+ volume: { value: 0.0 }
+ },
+ vertexShader: `
+ varying vec2 vUv;
+ void main()
+ {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
+ vUv = uv;
+ }
+ `,
+ fragmentShader: `
+ uniform float time;
+ uniform float volume;
+ varying vec2 vUv;
+
+ void main() {
+ float step = step(volume, vUv.x);
+ gl_FragColor = mix(vec4(vec3(0.8), 1.0), vec4(vec3(0.2), 1.0), step);
+ }
+ `
+});
+VolumeControlsMaterial.side = FrontSide;
+
const uiZ = 0.001;
const BUTTON_HEIGHT = 0.2;
const BIG_BUTTON_SCALE: ArrayVec3 = [0.8, 0.8, 0.8];
const BUTTON_SCALE: ArrayVec3 = [0.6, 0.6, 0.6];
+const SMALL_BUTTON_SCALE: ArrayVec3 = [0.4, 0.4, 0.4];
const BUTTON_WIDTH = 0.2;
function Slider({ trackRef, headRef, ...props }: any) {
@@ -45,6 +71,33 @@ function Slider({ trackRef, headRef, ...props }: any) {
);
}
+interface VolumeButtonProps extends Attrs {
+ text: string;
+}
+
+function VolumeButton(props: VolumeButtonProps) {
+ return (
+
+ );
+}
+
+function VolumeControls({ volUpRef, volDownRef, ...props }: any) {
+ return (
+
+
+
+
+
+ );
+}
+
interface VideoButtonProps extends Attrs {
buttonIcon: string;
}
@@ -87,13 +140,25 @@ export function VideoMenuPrefab() {
const playIndicatorRef = createRef();
const pauseIndicatorRef = createRef();
const snapRef = createRef();
+ const volUpRef = createRef();
+ const volDownRef = createRef();
const halfHeight = 9 / 16 / 2;
return (
+
);
}
diff --git a/src/utils/jsx-entity.ts b/src/utils/jsx-entity.ts
index 3803b90f9a..15e9e9c9f8 100644
--- a/src/utils/jsx-entity.ts
+++ b/src/utils/jsx-entity.ts
@@ -290,6 +290,8 @@ export interface JSXComponentData extends ComponentData {
playIndicatorRef: Ref;
pauseIndicatorRef: Ref;
snapRef: Ref;
+ volUpRef: Ref;
+ volDownRef: Ref;
};
videoMenuItem?: true;
cursorRaycastable?: true;