Skip to content

Commit

Permalink
Merge pull request #6452 from mozilla/bitecs-video-menu-volume
Browse files Browse the repository at this point in the history
Volume controls in the video menu
  • Loading branch information
keianhzo authored Jan 25, 2024
2 parents ced6609 + 889701f commit 8411cd7
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
37 changes: 35 additions & 2 deletions src/bit-systems/video-menu-system.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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]);
Expand Down Expand Up @@ -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);
Expand All @@ -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)!;
Expand All @@ -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);
}
}
}

Expand Down
70 changes: 68 additions & 2 deletions src/prefabs/video-menu.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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) {
Expand All @@ -45,6 +71,33 @@ function Slider({ trackRef, headRef, ...props }: any) {
);
}

interface VolumeButtonProps extends Attrs {
text: string;
}

function VolumeButton(props: VolumeButtonProps) {
return (
<Button3D
name={props.name}
scale={SMALL_BUTTON_SCALE}
width={BUTTON_WIDTH}
height={BUTTON_HEIGHT}
type={BUTTON_TYPES.ACTION}
{...props}
/>
);
}

function VolumeControls({ volUpRef, volDownRef, ...props }: any) {
return (
<entity {...props} name="Volume Controls">
<entity name="Volume Bar" object3D={new Mesh(new PlaneBufferGeometry(0.5, 0.05), VolumeControlsMaterial)} />
<VolumeButton name="Decrease Volume button" text="-" ref={volDownRef} position={[-0.35, 0, 0]} />
<VolumeButton name="Increase Volume Button" text="+" ref={volUpRef} position={[0.35, 0, 0]} />
</entity>
);
}

interface VideoButtonProps extends Attrs {
buttonIcon: string;
}
Expand Down Expand Up @@ -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 (
<entity
name="Video Menu"
objectMenuTransform={{ center: false }}
videoMenu={{ sliderRef, timeLabelRef, headRef, trackRef, playIndicatorRef, pauseIndicatorRef, snapRef }}
videoMenu={{
sliderRef,
timeLabelRef,
headRef,
trackRef,
playIndicatorRef,
pauseIndicatorRef,
snapRef,
volUpRef,
volDownRef
}}
>
<Label
name="Time Label"
Expand All @@ -106,6 +171,7 @@ export function VideoMenuPrefab() {
<Slider ref={sliderRef} trackRef={trackRef} headRef={headRef} position={[0, -halfHeight + 0.025, uiZ]} />
<VideoActionButton ref={playIndicatorRef} name={"Play Button"} buttonIcon={playImageUrl} />
<VideoActionButton ref={pauseIndicatorRef} name={"Pause Button"} buttonIcon={pauseImageUrl} />
<VolumeControls volUpRef={volUpRef} volDownRef={volDownRef} position={[0, -0.15, uiZ]} />
</entity>
);
}
2 changes: 2 additions & 0 deletions src/utils/jsx-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ export interface JSXComponentData extends ComponentData {
playIndicatorRef: Ref;
pauseIndicatorRef: Ref;
snapRef: Ref;
volUpRef: Ref;
volDownRef: Ref;
};
videoMenuItem?: true;
cursorRaycastable?: true;
Expand Down

0 comments on commit 8411cd7

Please sign in to comment.