Camera now show up when someone is moving and hides 5 seconds after we stop moving.

Also, added an animation to show/hide the webcam.
This commit is contained in:
David Négrier 2021-05-20 18:05:03 +02:00
parent 8af8ccd54b
commit d32df13f1b
6 changed files with 146 additions and 10 deletions

View file

@ -73,7 +73,6 @@
<img id="microphone-close" src="resources/logos/microphone-close.svg"> <img id="microphone-close" src="resources/logos/microphone-close.svg">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="cowebsite" class="cowebsite hidden"> <div id="cowebsite" class="cowebsite hidden">
@ -108,7 +107,7 @@
</div> </div>
</div> </div>
<div class="audioplayer"> <div class="audioplayer">
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations"> <label id="label-audioplayer_decrease_while_talking" for="audioplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
reduce in conversations reduce in conversations
<input type="checkbox" id="audioplayer_decrease_while_talking" checked /> <input type="checkbox" id="audioplayer_decrease_while_talking" checked />
</label> </label>

View file

@ -2,6 +2,7 @@ import {PlayerAnimationDirections} from "./Animation";
import type {GameScene} from "../Game/GameScene"; import type {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character"; import {Character} from "../Entity/Character";
import {userMovingStore} from "../../Stores/GameStore";
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu"; import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu";
export const hasMovedEventName = "hasMoved"; export const hasMovedEventName = "hasMoved";
@ -86,6 +87,7 @@ export class Player extends Character {
this.previousDirection = direction; this.previousDirection = direction;
} }
this.wasMoving = moving; this.wasMoving = moving;
userMovingStore.set(moving);
} }
public isMoving(): boolean { public isMoving(): boolean {
@ -99,7 +101,7 @@ export class Player extends Character {
this.openEmoteMenu(emotes); this.openEmoteMenu(emotes);
} }
} }
isClickable(): boolean { isClickable(): boolean {
return true; return true;
} }
@ -113,13 +115,13 @@ export class Player extends Character {
this.playEmote(item.name); this.playEmote(item.name);
}); });
} }
closeEmoteMenu(): void { closeEmoteMenu(): void {
if (!this.emoteMenu) return; if (!this.emoteMenu) return;
this.emoteMenu.destroy(); this.emoteMenu.destroy();
this.emoteMenu = null; this.emoteMenu = null;
} }
destroy() { destroy() {
this.scene.events.removeListener('postupdate', this.updateListener); this.scene.events.removeListener('postupdate', this.updateListener);
super.destroy(); super.destroy();

View file

@ -0,0 +1,3 @@
import { derived, writable, Writable } from "svelte/store";
export const userMovingStore = writable(false);

View file

@ -2,6 +2,8 @@ import {derived, get, Readable, readable, writable, Writable} from "svelte/store
import {peerStore} from "./PeerStore"; import {peerStore} from "./PeerStore";
import {localUserStore} from "../Connexion/LocalUserStore"; import {localUserStore} from "../Connexion/LocalUserStore";
import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap"; import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
import {userMovingStore} from "./GameStore";
import {HtmlUtils} from "../WebRtc/HtmlUtils";
/** /**
* A store that contains the camera state requested by the user (on or off). * A store that contains the camera state requested by the user (on or off).
@ -110,6 +112,108 @@ function createPrivacyShutdownStore() {
export const privacyShutdownStore = createPrivacyShutdownStore(); export const privacyShutdownStore = createPrivacyShutdownStore();
/**
* A store containing whether the webcam was enabled in the last 10 seconds
*/
const enabledWebCam10secondsAgoStore = readable(false, function start(set) {
let timeout: NodeJS.Timeout|null = null;
const unsubscribe = requestedCameraState.subscribe((enabled) => {
if (enabled === true) {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
set(false);
}, 10000);
set(true);
} else {
set(false);
}
})
return function stop() {
unsubscribe();
};
});
/**
* A store containing whether the webcam was enabled in the last 5 seconds
*/
const userMoved5SecondsAgoStore = readable(false, function start(set) {
let timeout: NodeJS.Timeout|null = null;
const unsubscribe = userMovingStore.subscribe((moving) => {
if (moving === true) {
if (timeout) {
clearTimeout(timeout);
}
set(true);
} else {
timeout = setTimeout(() => {
set(false);
}, 5000);
}
})
return function stop() {
unsubscribe();
};
});
/**
* A store containing whether the current page is visible or not.
*/
const mouseInBottomRight = readable(false, function start(set) {
let lastInBottomRight = false;
const gameDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game');
const detectInBottomRight = (event: MouseEvent) => {
// TODO: not relative to window but to canvas!!!
// use phaser?
const rect = gameDiv.getBoundingClientRect();
const inBottomRight = event.x - rect.left > rect.width * 3 / 4 && event.y - rect.top > rect.height * 3 / 4;
if (inBottomRight !== lastInBottomRight) {
lastInBottomRight = inBottomRight;
set(inBottomRight);
}
};
document.addEventListener('mousemove', detectInBottomRight);
return function stop() {
document.removeEventListener('mousemove', detectInBottomRight);
}
/*const mouseEnter = () => {
set(true);
console.log('enter')
}
const mouseLeave = () => set(false);
const bottomLeftZone = HtmlUtils.getElementByIdOrFail('bottom-left-zone');
bottomLeftZone.addEventListener('mouseenter', mouseEnter);
bottomLeftZone.addEventListener('mouseleave', mouseLeave);
return function stop() {
bottomLeftZone.removeEventListener('mouseenter', mouseEnter);
bottomLeftZone.removeEventListener('mouseleave', mouseLeave);
};*/
});
/**
* A store that contains "true" if the webcam should be stopped for energy efficiency reason - i.e. we are not moving and not in a conversation.
*/
export const cameraEnergySavingStore = derived([userMoved5SecondsAgoStore, peerStore, enabledWebCam10secondsAgoStore, mouseInBottomRight], ([$userMoved5SecondsAgoStore,$peerStore, $enabledWebCam10secondsAgoStore, $mouseInBottomRight]) => {
// TODO: enable when mouse is hovering near the webcam
// TODO: add animation to show/hide webcam
return !$mouseInBottomRight && !$userMoved5SecondsAgoStore && $peerStore.size === 0 && !$enabledWebCam10secondsAgoStore;
});
/** /**
* A store that contains video constraints. * A store that contains video constraints.
*/ */
@ -192,6 +296,7 @@ export const mediaStreamConstraintsStore = derived(
videoConstraintStore, videoConstraintStore,
audioConstraintStore, audioConstraintStore,
privacyShutdownStore, privacyShutdownStore,
cameraEnergySavingStore,
], ( ], (
[ [
$requestedCameraState, $requestedCameraState,
@ -201,6 +306,7 @@ export const mediaStreamConstraintsStore = derived(
$videoConstraintStore, $videoConstraintStore,
$audioConstraintStore, $audioConstraintStore,
$privacyShutdownStore, $privacyShutdownStore,
$cameraEnergySavingStore,
], set ], set
) => { ) => {
@ -236,6 +342,12 @@ export const mediaStreamConstraintsStore = derived(
currentVideoConstraint = false; currentVideoConstraint = false;
} }
// Disable webcam for energy reasons (the user is not moving and we are talking to noone)
if ($cameraEnergySavingStore === true) {
currentVideoConstraint = false;
currentAudioConstraint = false;
}
// Let's make the changes only if the new value is different from the old one. // Let's make the changes only if the new value is different from the old one.
if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) { if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) {
previousComputedVideoConstraint = currentVideoConstraint; previousComputedVideoConstraint = currentVideoConstraint;
@ -415,3 +527,4 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStreamStore) => { export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStreamStore) => {
return $localStreamStore.constraints; return $localStreamStore.constraints;
}); });

View file

@ -142,15 +142,15 @@ export class MediaManager {
} }
if (result.constraints.video !== false) { if (result.constraints.video !== false) {
this.enableCameraStyle(); HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide');
} else { } else {
this.disableCameraStyle(); HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide');
} }/*
if (result.constraints.audio !== false) { if (result.constraints.audio !== false) {
this.enableMicrophoneStyle(); this.enableMicrophoneStyle();
} else { } else {
this.disableMicrophoneStyle(); this.disableMicrophoneStyle();
} }*/
this.localStream = result.stream; this.localStream = result.stream;
this.myCamVideo.srcObject = this.localStream; this.myCamVideo.srcObject = this.localStream;
@ -158,6 +158,21 @@ export class MediaManager {
// TODO: migrate all listeners to the store directly. // TODO: migrate all listeners to the store directly.
this.triggerUpdatedLocalStreamCallbacks(result.stream); this.triggerUpdatedLocalStreamCallbacks(result.stream);
}); });
requestedCameraState.subscribe((enabled) => {
if (enabled) {
this.enableCameraStyle();
} else {
this.disableCameraStyle();
}
});
requestedMicrophoneState.subscribe((enabled) => {
if (enabled) {
this.enableMicrophoneStyle();
} else {
this.disableMicrophoneStyle();
}
});
} }
public updateScene(){ public updateScene(){

View file

@ -143,6 +143,11 @@ body .message-info.warning{
bottom: 30px; bottom: 30px;
border-radius: 15px 15px 15px 15px; border-radius: 15px 15px 15px 15px;
max-height: 20%; max-height: 20%;
transition: right 350ms;
}
#div-myCamVideo.hide {
right: -20vw;
} }
video#myCamVideo{ video#myCamVideo{
@ -216,7 +221,6 @@ video#myCamVideo{
border-radius: 48px; border-radius: 48px;
transform: translateY(20px); transform: translateY(20px);
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
margin-bottom: 20px;
margin: 0 4%; margin: 0 4%;
} }
.btn-cam-action div.disabled { .btn-cam-action div.disabled {