merged latest dev

This commit is contained in:
_Bastler 2021-05-30 15:29:28 +02:00
commit 85c9393962
18 changed files with 359 additions and 311 deletions

View File

@ -44,32 +44,6 @@
</aside> </aside>
<div id="chat-mode" class="chat-mode three-col" style="display: none;"> <div id="chat-mode" class="chat-mode three-col" style="display: none;">
</div> </div>
<div id="activeCam" class="activeCam">
<div id="div-myCamVideo" class="video-container nes-container is-rounded is-dark">
<video id="myCamVideo" autoplay muted></video>
<div id="mySoundMeter" class="sound-progress">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
<div class="btn-cam-action">
<div id="btn-micro" class="btn-micro nes-btn is-dark">
<img id="microphone" src="resources/logos/microphone.svg">
<img id="microphone-close" src="resources/logos/microphone-close.svg">
</div>
<div id="btn-video" class="btn-video nes-btn is-dark">
<img id="cinema" src="resources/logos/cinema.svg">
<img id="cinema-close" src="resources/logos/cinema-close.svg">
</div>
<div id="btn-monitor" class="btn-monitor nes-btn is-dark">
<img id="monitor" src="resources/logos/monitor.svg">
<img id="monitor-close" src="resources/logos/monitor-close.svg">
</div>
</div>
</div> </div>
</div> </div>
<div id="cowebsite" class="cowebsite hidden"> <div id="cowebsite" class="cowebsite hidden">

View File

@ -1,11 +1,18 @@
<script lang="typescript"> <script lang="typescript">
import MenuIcon from "./Menu/MenuIcon.svelte"; import MenuIcon from "./Menu/MenuIcon.svelte";
import {menuIconVisible} from "../Stores/MenuStore"; import {menuIconVisible} from "../Stores/MenuStore";
import {gameOverlayVisibilityStore} from "../Stores/MediaStore";
import CameraControls from "./CameraControls.svelte";
import MyCamera from "./MyCamera.svelte";
</script> </script>
<div> <div>
{#if $gameOverlayVisibilityStore}
<!-- {#if $menuIconVisible} <!-- {#if $menuIconVisible}
<MenuIcon /> <MenuIcon />
{/if} --> {/if} -->
<MyCamera></MyCamera>
<CameraControls></CameraControls>
{/if}
</div> </div>

View File

@ -0,0 +1,59 @@
<script lang="typescript">
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
import monitorImg from "./images/monitor.svg";
import monitorCloseImg from "./images/monitor-close.svg";
import cinemaImg from "./images/cinema.svg";
import cinemaCloseImg from "./images/cinema-close.svg";
import microphoneImg from "./images/microphone.svg";
import microphoneCloseImg from "./images/microphone-close.svg";
function screenSharingClick(): void {
if ($requestedScreenSharingState === true) {
requestedScreenSharingState.disableScreenSharing();
} else {
requestedScreenSharingState.enableScreenSharing();
}
}
function cameraClick(): void {
if ($requestedCameraState === true) {
requestedCameraState.disableWebcam();
} else {
requestedCameraState.enableWebcam();
}
}
function microphoneClick(): void {
if ($requestedMicrophoneState === true) {
requestedMicrophoneState.disableMicrophone();
} else {
requestedMicrophoneState.enableMicrophone();
}
}
</script>
<div class="btn-cam-action">
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
{#if $requestedMicrophoneState}
<img src={microphoneImg} alt="Turn on microphone">
{:else}
<img src={microphoneCloseImg} alt="Turn off microphone">
{/if}
</div>
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState}>
{#if $requestedCameraState}
<img src={cinemaImg} alt="Turn on webcam">
{:else}
<img src={cinemaCloseImg} alt="Turn off webcam">
{/if}
</div>
<div class="btn-monitor nes-btn is-dark" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
{#if $requestedScreenSharingState}
<img src={monitorImg} alt="Start screen sharing">
{:else}
<img src={monitorCloseImg} alt="Stop screen sharing">
{/if}
</div>
</div>

View File

@ -0,0 +1,46 @@
<script lang="typescript">
import {localStreamStore} from "../Stores/MediaStore";
import SoundMeterWidget from "./SoundMeterWidget.svelte";
import {onDestroy} from "svelte";
function srcObject(node, stream) {
node.srcObject = stream;
return {
update(newStream) {
if (node.srcObject != newStream) {
node.srcObject = newStream
}
}
}
}
let stream : MediaStream|null;
/*$: {
if ($localStreamStore.type === 'success') {
stream = $localStreamStore.stream;
} else {
stream = null;
}
}*/
const unsubscribe = localStreamStore.subscribe(value => {
if (value.type === 'success') {
stream = value.stream;
} else {
stream = null;
}
});
onDestroy(unsubscribe);
</script>
<div>
<div class="video-container nes-container is-rounded is-dark div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted></video>
<!-- {#if stream}
<SoundMeterWidget stream={stream}></SoundMeterWidget>
{/if} -->
</div>
</div>

View File

@ -0,0 +1,36 @@
<script lang="typescript">
import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {onDestroy} from "svelte";
export let stream: MediaStream;
let volume = 0;
console.log('stream', stream);
if (stream.getAudioTracks().length > 0) {
const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext());
const timeout = setInterval(() => {
try{
volume = parseInt((soundMeter.getVolume() / 10).toFixed(0));
console.log(volume);
}catch(err){
}
}, 100);
onDestroy(() => {
clearInterval(timeout);
})
}
</script>
<div class="sound-progress" class:active={stream?.getAudioTracks().length > 0}>
<span class:active={volume > 1}></span>
<span class:active={volume > 2}></span>
<span class:active={volume > 3}></span>
<span class:active={volume > 4}></span>
<span class:active={volume > 5}></span>
</div>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g>
<g>
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
</g>
</g>
</g>
<g>
<g>
</g>
</g>
<rect x="134.8" y="-45.3" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 376.0669 224.8258)" class="st0" width="19.6" height="460.7"/>
<path class="st0" d="M90.6,83.3c-0.2-2.2-1.3-8.9-6.7-14.9c-5.4-5.9-11.9-7.6-14.1-8.1C59.7,49.2,49.5,38,39.4,26.8
c24.3-9.8,52-4.4,70.2,13.6c19.9,19.7,24.7,50.8,11.5,76.4C110.9,105.6,100.8,94.5,90.6,83.3z"/>
<path class="st0" d="M10.1,51.6c9.4,10.2,18.8,20.4,28.2,30.6c-0.2,1.8-1.4,11.7,5.5,20.5c8.2,10.3,20.7,10.2,22.1,10.1
c9.2,10.3,18.5,20.6,27.7,30.8c-4.8,2.3-24.6,11.2-48.3,4.1c-6-1.8-20.7-7.3-32.1-22C-0.3,108.1-0.2,89.1,0.1,83.4
C0.8,68,6.8,56.8,10.1,51.6z"/>
<g>
<path class="st0" d="M243.4,178.2c0.1,24.5,0.2,49,0.2,73.5c-30.7-33.8-61.3-67.7-92-101.5c5.9,3.9,20.9,12.4,41.6,12.4
c16,0,28.2-5.2,34.4-8.4c2.5,1.5,7,4.6,10.7,10.3C242,170,243,175.4,243.4,178.2z"/>
<g>
<path class="st0" d="M211.2,311C150.8,258.7,90.4,206.5,30,154.2c6.1,3.1,18.2,8.4,34.4,8.4c18.1,0,31.5-6.5,37.5-9.9
c44.5,49,89.1,98.1,133.6,147.1c-1.8,2.1-5.3,5.5-10.6,8.1C219.2,310.6,214,311,211.2,311z"/>
<path class="st0" d="M46.8,311C36,267.7,25.2,224.3,14.4,181c0.1-3.2,0.7-11.3,6.5-18.8c3.1-4.1,6.7-6.6,9.1-8
C90.4,206.5,150.8,258.7,211.2,311C156.4,311,101.6,311,46.8,311z"/>
<path class="st0" d="M14.4,278.6L14.4,278.6c0-32.5,0-65.1,0-97.6c10.8,43.3,21.6,86.7,32.4,130c-2.6,0-12.7-0.4-21.5-8.1
C14.7,293.5,14.4,280.7,14.4,278.6z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g>
<g>
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
<path class="st0" d="M64.4,150.6c35.6,0,64.4-28.8,64.4-64.4S100,21.8,64.4,21.8S0,50.6,0,86.2C-0.4,121.8,28.8,150.6,64.4,150.6
z M64.4,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4S38,100.6,38,86.2C37.6,71.4,49.6,59.8,64.4,59.8z"/>
<path class="st0" d="M227.6,154.2c-10.4,5.2-22,8.4-34.4,8.4c-15.2,0-29.6-4.4-41.6-12.4H106c-12,8-26.4,12.4-41.6,12.4
c-12.4,0-24-2.8-34.4-8.4c-9.2,5.2-15.6,15.6-15.6,26.8v97.6c0,18,14.8,32.4,32.4,32.4h164.4c18,0,32.4-14.8,32.4-32.4V181
C243.2,169.8,236.8,159.4,227.6,154.2z"/>
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<rect x="257" y="-47.9" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 643.9641 283.6469)" class="st0" width="20.4" height="628.3"/>
<g>
<g>
<path class="st0" d="M333.6,250.3c-52.6-43.9-105.1-87.9-157.7-131.8c0-17.9,0-35.8,0-53.6c6.5-38.6,40.3-67,79.3-66.8
c38.6,0.2,71.9,28.5,78.4,66.8C333.6,126.7,333.6,188.5,333.6,250.3z"/>
<path class="st0" d="M322.6,279.9c-48.9-53.8-97.8-107.6-146.6-161.4l0,0c52.6,43.9,105.1,87.9,157.7,131.8
c-0.2,1.6-0.5,3.3-0.9,5C330.5,265.2,326.6,273.5,322.6,279.9z"/>
</g>
<path class="st0" d="M292.5,308.1c-2.3,1.2-39.5,20.3-76.7-1c-36.4-20.8-39.4-61.2-39.6-64.1c-0.1-21-0.1-42.1-0.2-63.1
C214.8,222.6,253.6,265.3,292.5,308.1z"/>
</g>
<path class="st0" d="M431.6,238.5c-0.9-8.4-8.5-14.4-16.6-13.5c-7.9,0.9-13.9,8.1-13.2,16.3c-0.1,13.3-2.2,34.6-12.6,57.9
c-6.3,14.2-14,25.2-20.6,33.1c6.8,7.5,13.6,14.9,20.3,22.4c9.5-10.9,23.4-29.7,32.8-56.3C430.3,273.9,431.8,252.5,431.6,238.5z"/>
<line class="st0" x1="354.5" y1="347.2" x2="374.6" y2="369.4"/>
<path class="st0" d="M338.5,359.9c6.8,7.4,13.5,14.9,20.3,22.3c-52.6,37.6-121.5,43.7-179.2,15.8c-60.3-29.1-98.9-90.7-99.3-158.2
c0-8.2,6.8-15,15-15s15,6.8,15,15c0.1,13.5,2.4,54.4,32.4,91.6c4.2,5.2,45.1,54.1,113.3,54.1C297,385.6,326.7,367.9,338.5,359.9z"/>
<rect x="241" y="409.6" class="st0" width="29.9" height="102.3"/>
<path class="st0" d="M304.2,511.9h-97.1c-8-0.4-14.3-7.1-14.3-15c0-8.1,6.7-14.9,15-15c31.7,0,63.4,0.1,95.1,0.1
c8.9-0.6,16.3,6.5,16.3,14.9C319.2,504.8,312.6,511.7,304.2,511.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 937 B

After

Width:  |  Height:  |  Size: 937 B

View File

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 884 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -41,7 +41,7 @@ export class LoginScene extends ResizableScene {
} }
pErrorElement.innerHTML = ''; pErrorElement.innerHTML = '';
if(inputElement.value && !isUserNameValid(inputElement.value)){ if(inputElement.value && !isUserNameValid(inputElement.value)){
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.'; pErrorElement.innerHTML = 'Invalid user name: No spaces are allowed.';
} }
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();

View File

@ -8,33 +8,11 @@ import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable"; import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
import { import {
gameOverlayVisibilityStore, localStreamStore, gameOverlayVisibilityStore, localStreamStore,
mediaStreamConstraintsStore,
requestedCameraState,
requestedMicrophoneState
} from "../Stores/MediaStore"; } from "../Stores/MediaStore";
import { import {
requestedScreenSharingState,
screenSharingAvailableStore,
screenSharingLocalStreamStore screenSharingLocalStreamStore
} from "../Stores/ScreenSharingStore"; } from "../Stores/ScreenSharingStore";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
const videoConstraint: boolean|MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() },
facingMode: "user",
resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778
};
const audioConstraint: boolean|MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl: false,
echoCancellation: true,
noiseSuppression: false
};
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
export type StartScreenSharingCallback = (media: MediaStream) => void; export type StartScreenSharingCallback = (media: MediaStream) => void;
export type StopScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void;
@ -43,30 +21,16 @@ export type ShowReportCallBack = (userId: string, userName: string|undefined) =>
export type HelpCameraSettingsCallBack = () => void; export type HelpCameraSettingsCallBack = () => void;
export class MediaManager { export class MediaManager {
localStream: MediaStream|null = null;
localScreenCapture: MediaStream|null = null;
private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>(); private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>();
myCamVideo: HTMLVideoElement;
cinemaClose: HTMLImageElement;
cinema: HTMLImageElement;
monitorClose: HTMLImageElement;
monitor: HTMLImageElement;
microphoneClose: HTMLImageElement;
microphone: HTMLImageElement;
webrtcInAudio: HTMLAudioElement; webrtcInAudio: HTMLAudioElement;
//FIX ME SOUNDMETER: check stalability of sound meter calculation //FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement; //mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement; private webrtcOutAudio: HTMLAudioElement;
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>(); startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>(); stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>(); showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>(); helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
private focused : boolean = true; private focused : boolean = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>(); private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
@ -80,54 +44,11 @@ export class MediaManager {
constructor() { constructor() {
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in'); this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-out'); this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-out');
this.webrtcInAudio.volume = 0.2; this.webrtcInAudio.volume = 0.2;
this.webrtcOutAudio.volume = 0.2; this.webrtcOutAudio.volume = 0.2;
this.microphoneBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-micro');
this.microphoneClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone-close');
this.microphoneClose.style.display = "none";
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedMicrophoneState.enableMicrophone();
});
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedMicrophoneState.disableMicrophone();
});
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
this.cinemaClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema-close');
this.cinemaClose.style.display = "none";
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedCameraState.enableWebcam();
});
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedCameraState.disableWebcam();
});
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
this.monitorClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor-close');
this.monitorClose.style.display = "block";
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
//this.enableScreenSharing();
requestedScreenSharingState.enableScreenSharing();
});
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor.style.display = "none";
this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
//this.disableScreenSharing();
requestedScreenSharingState.disableScreenSharing();
});
this.pingCameraStatus(); this.pingCameraStatus();
//FIX ME SOUNDMETER: check stability of sound meter calculation //FIX ME SOUNDMETER: check stability of sound meter calculation
@ -147,40 +68,8 @@ export class MediaManager {
}, this.userInputManager); }, this.userInputManager);
return; return;
} }
if (result.constraints.video !== false) {
HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide');
} else {
HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide');
}/*
if (result.constraints.audio !== false) {
this.enableMicrophoneStyle();
} else {
this.disableMicrophoneStyle();
}*/
this.localStream = result.stream;
this.myCamVideo.srcObject = this.localStream;
// TODO: migrate all listeners to the store directly.
this.triggerUpdatedLocalStreamCallbacks(result.stream);
}); });
requestedCameraState.subscribe((enabled) => {
if (enabled) {
this.enableCameraStyle();
} else {
this.disableCameraStyle();
}
});
requestedMicrophoneState.subscribe((enabled) => {
if (enabled) {
this.enableMicrophoneStyle();
} else {
this.disableMicrophoneStyle();
}
});
//let screenSharingStream : MediaStream|null;
screenSharingLocalStreamStore.subscribe((result) => { screenSharingLocalStreamStore.subscribe((result) => {
if (result.type === 'error') { if (result.type === 'error') {
console.error(result.error); console.error(result.error);
@ -191,38 +80,21 @@ export class MediaManager {
} }
if (result.stream !== null) { if (result.stream !== null) {
this.enableScreenSharingStyle();
mediaManager.localScreenCapture = result.stream;
// TODO: migrate this out of MediaManager
this.triggerStartedScreenSharingCallbacks(result.stream);
//screenSharingStream = result.stream;
this.addScreenSharingActiveVideo('me', DivImportance.Normal); this.addScreenSharingActiveVideo('me', DivImportance.Normal);
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = result.stream; HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = result.stream;
} else { } else {
this.disableScreenSharingStyle();
this.removeActiveScreenSharingVideo('me'); this.removeActiveScreenSharingVideo('me');
// FIXME: we need the old stream that is being stopped!
if (this.localScreenCapture) {
this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture);
this.localScreenCapture = null;
}
//screenSharingStream = null;
} }
}); });
screenSharingAvailableStore.subscribe((available) => { /*screenSharingAvailableStore.subscribe((available) => {
if (available) { if (available) {
document.querySelector('.btn-monitor')?.classList.remove('hide'); document.querySelector('.btn-monitor')?.classList.remove('hide');
} else { } else {
document.querySelector('.btn-monitor')?.classList.add('hide'); document.querySelector('.btn-monitor')?.classList.add('hide');
} }
}); });*/
} }
public updateScene(){ public updateScene(){
@ -230,40 +102,6 @@ export class MediaManager {
//this.updateSoudMeter(); //this.updateSoudMeter();
} }
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.add(callback);
}
public onStartScreenSharing(callback: StartScreenSharingCallback): void {
this.startScreenSharingCallBacks.add(callback);
}
public onStopScreenSharing(callback: StopScreenSharingCallback): void {
this.stopScreenSharingCallBacks.add(callback);
}
removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void {
this.updatedLocalStreamCallBacks.delete(callback);
}
private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void {
for (const callback of this.updatedLocalStreamCallBacks) {
callback(stream);
}
}
private triggerStartedScreenSharingCallbacks(stream: MediaStream): void {
for (const callback of this.startScreenSharingCallBacks) {
callback(stream);
}
}
private triggerStoppedScreenSharingCallbacks(stream: MediaStream): void {
for (const callback of this.stopScreenSharingCallBacks) {
callback(stream);
}
}
public showGameOverlay(): void { public showGameOverlay(): void {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active'); gameOverlay.classList.add('active');
@ -290,42 +128,6 @@ export class MediaManager {
gameOverlayVisibilityStore.hideGameOverlay(); gameOverlayVisibilityStore.hideGameOverlay();
} }
private enableCameraStyle(){
this.cinemaClose.style.display = "none";
this.cinemaBtn.classList.remove("disabled");
this.cinema.style.display = "block";
}
private disableCameraStyle(){
this.cinemaClose.style.display = "block";
this.cinema.style.display = "none";
this.cinemaBtn.classList.add("disabled");
}
private enableMicrophoneStyle(){
this.microphoneClose.style.display = "none";
this.microphone.style.display = "block";
this.microphoneBtn.classList.remove("disabled");
}
private disableMicrophoneStyle(){
this.microphoneClose.style.display = "block";
this.microphone.style.display = "none";
this.microphoneBtn.classList.add("disabled");
}
private enableScreenSharingStyle(){
this.monitorClose.style.display = "none";
this.monitor.style.display = "block";
this.monitorBtn.classList.add("enabled");
}
private disableScreenSharingStyle(){
this.monitorClose.style.display = "block";
this.monitor.style.display = "none";
this.monitorBtn.classList.remove("enabled");
}
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
this.webrtcInAudio.play(); this.webrtcInAudio.play();
const userId = ''+user.userId const userId = ''+user.userId

View File

@ -19,7 +19,7 @@ export class ScreenSharingPeer extends Peer {
public _connected: boolean = false; public _connected: boolean = false;
private userId: number; private userId: number;
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, stream: MediaStream | null) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
//reconnectTimer: 10000, //reconnectTimer: 10000,
@ -81,7 +81,9 @@ export class ScreenSharingPeer extends Peer {
this._onFinish(); this._onFinish();
}); });
this.pushScreenSharingToRemoteUser(); if (stream) {
this.addStream(stream);
}
} }
private sendWebrtcScreenSharingSignal(data: unknown) { private sendWebrtcScreenSharingSignal(data: unknown) {
@ -141,16 +143,6 @@ export class ScreenSharingPeer extends Peer {
} }
} }
private pushScreenSharingToRemoteUser() {
const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture;
if(!localScreenCapture){
return;
}
this.addStream(localScreenCapture);
return;
}
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) { public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
this.removeStream(stream); this.removeStream(stream);
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true}))); this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true})));

View File

@ -15,7 +15,10 @@ import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager"; import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager"; import {blackListManager} from "./BlackListManager";
import {get} from "svelte/store"; import {get} from "svelte/store";
import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore"; import {localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore} from "../Stores/MediaStore";
import {screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore";
import {DivImportance, layoutManager} from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils";
export interface UserSimplePeerInterface{ export interface UserSimplePeerInterface{
userId: number; userId: number;
@ -39,9 +42,9 @@ export class SimplePeer {
private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>(); private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>();
private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>(); private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>();
private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback;
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
private readonly unsubscribers: (() => void)[] = [];
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>(); private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
private readonly userId: number; private readonly userId: number;
private lastWebrtcUserName: string|undefined; private lastWebrtcUserName: string|undefined;
@ -49,13 +52,32 @@ export class SimplePeer {
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) { constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
// We need to go through this weird bound function pointer in order to be able to "free" this reference later. // We need to go through this weird bound function pointer in order to be able to "free" this reference later.
this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this);
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this); this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this);
mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); this.unsubscribers.push(localStreamStore.subscribe((streamResult) => {
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback); this.sendLocalVideoStream(streamResult);
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback); }));
let localScreenCapture: MediaStream|null = null;
this.unsubscribers.push(screenSharingLocalStreamStore.subscribe((streamResult) => {
if (streamResult.type === 'error') {
// Let's ignore screen sharing errors, we will deal with those in a different way.
return;
}
if (streamResult.stream !== null) {
localScreenCapture = streamResult.stream;
this.sendLocalScreenSharingStream(localScreenCapture);
} else {
if (localScreenCapture) {
this.stopLocalScreenSharingStream(localScreenCapture);
localScreenCapture = null;
}
}
}));
this.userId = Connection.getUserId(); this.userId = Connection.getUserId();
this.initialise(); this.initialise();
} }
@ -106,13 +128,19 @@ export class SimplePeer {
if(!user.initiator){ if(!user.initiator){
return; return;
} }
this.createPeerConnection(user); const streamResult = get(localStreamStore);
let stream : MediaStream | null = null;
if (streamResult.type === 'success' && streamResult.stream) {
stream = streamResult.stream;
}
this.createPeerConnection(user, stream);
} }
/** /**
* create peer connection to bind users * create peer connection to bind users
*/ */
private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null { private createPeerConnection(user : UserSimplePeerInterface, localStream: MediaStream | null) : VideoPeer | null {
const peerConnection = this.PeerConnectionArray.get(user.userId) const peerConnection = this.PeerConnectionArray.get(user.userId)
if (peerConnection) { if (peerConnection) {
if (peerConnection.destroyed) { if (peerConnection.destroyed) {
@ -122,11 +150,11 @@ export class SimplePeer {
if (!peerConnexionDeleted) { if (!peerConnexionDeleted) {
throw 'Error to delete peer connection'; throw 'Error to delete peer connection';
} }
this.createPeerConnection(user); //return this.createPeerConnection(user, localStream);
} else { } else {
peerConnection.toClose = false; peerConnection.toClose = false;
return null;
} }
return null;
} }
let name = user.name; let name = user.name;
@ -144,7 +172,7 @@ export class SimplePeer {
this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcUserName = user.webRtcUser;
this.lastWebrtcPassword = user.webRtcPassword; this.lastWebrtcPassword = user.webRtcPassword;
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection); const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection, localStream);
//permit to send message //permit to send message
mediaManager.addSendMessageCallback(user.userId,(message: string) => { mediaManager.addSendMessageCallback(user.userId,(message: string) => {
@ -155,8 +183,9 @@ export class SimplePeer {
// When a connection is established to a video stream, and if a screen sharing is taking place, // When a connection is established to a video stream, and if a screen sharing is taking place,
// the user sharing screen should also initiate a connection to the remote user! // the user sharing screen should also initiate a connection to the remote user!
peer.on('connect', () => { peer.on('connect', () => {
if (mediaManager.localScreenCapture) { const streamResult = get(screenSharingLocalStreamStore);
this.sendLocalScreenSharingStreamToUser(user.userId); if (streamResult.type === 'success' && streamResult.stream !== null) {
this.sendLocalScreenSharingStreamToUser(user.userId, streamResult.stream);
} }
}); });
@ -175,7 +204,7 @@ export class SimplePeer {
/** /**
* create peer connection to bind users * create peer connection to bind users
*/ */
private createPeerScreenSharingConnection(user : UserSimplePeerInterface) : ScreenSharingPeer | null{ private createPeerScreenSharingConnection(user : UserSimplePeerInterface, stream: MediaStream | null) : ScreenSharingPeer | null{
const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId); const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId);
if(peerConnection){ if(peerConnection){
if(peerConnection.destroyed){ if(peerConnection.destroyed){
@ -185,7 +214,7 @@ export class SimplePeer {
if(!peerConnexionDeleted){ if(!peerConnexionDeleted){
throw 'Error to delete peer connection'; throw 'Error to delete peer connection';
} }
this.createPeerConnection(user); this.createPeerConnection(user, stream);
}else { }else {
peerConnection.toClose = false; peerConnection.toClose = false;
} }
@ -204,7 +233,7 @@ export class SimplePeer {
user.webRtcPassword = this.lastWebrtcPassword; user.webRtcPassword = this.lastWebrtcPassword;
} }
const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection); const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection, stream);
this.PeerScreenSharingConnectionArray.set(user.userId, peer); this.PeerScreenSharingConnectionArray.set(user.userId, peer);
for (const peerConnectionListener of this.peerConnectionListeners) { for (const peerConnectionListener of this.peerConnectionListeners) {
@ -294,7 +323,9 @@ export class SimplePeer {
* Unregisters any held event handler. * Unregisters any held event handler.
*/ */
public unregister() { public unregister() {
mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback); for (const unsubscriber of this.unsubscribers) {
unsubscriber();
}
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -302,7 +333,13 @@ export class SimplePeer {
try { try {
//if offer type, create peer connection //if offer type, create peer connection
if(data.signal.type === "offer"){ if(data.signal.type === "offer"){
this.createPeerConnection(data); const streamResult = get(localStreamStore);
let stream : MediaStream | null = null;
if (streamResult.type === 'success' && streamResult.stream) {
stream = streamResult.stream;
}
this.createPeerConnection(data, stream);
} }
const peer = this.PeerConnectionArray.get(data.userId); const peer = this.PeerConnectionArray.get(data.userId);
if (peer !== undefined) { if (peer !== undefined) {
@ -318,18 +355,26 @@ export class SimplePeer {
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) { private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
if (blackListManager.isBlackListed(data.userId)) return; if (blackListManager.isBlackListed(data.userId)) return;
console.log("receiveWebrtcScreenSharingSignal", data); console.log("receiveWebrtcScreenSharingSignal", data);
const streamResult = get(screenSharingLocalStreamStore);
let stream : MediaStream | null = null;
if (streamResult.type === 'success' && streamResult.stream !== null) {
stream = streamResult.stream;
}
try { try {
//if offer type, create peer connection //if offer type, create peer connection
if(data.signal.type === "offer"){ if(data.signal.type === "offer"){
this.createPeerScreenSharingConnection(data); this.createPeerScreenSharingConnection(data, stream);
} }
const peer = this.PeerScreenSharingConnectionArray.get(data.userId); const peer = this.PeerScreenSharingConnectionArray.get(data.userId);
if (peer !== undefined) { if (peer !== undefined) {
peer.signal(data.signal); peer.signal(data.signal);
} else { } else {
console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal'); console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal');
console.info('tentative to create new peer connexion'); console.info('Attempt to create new peer connexion');
this.sendLocalScreenSharingStreamToUser(data.userId); if (stream) {
this.sendLocalScreenSharingStreamToUser(data.userId, stream);
}
} }
} catch (e) { } catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e); console.error(`receiveWebrtcSignal => ${data.userId}`, e);
@ -339,21 +384,19 @@ export class SimplePeer {
} }
} }
private pushVideoToRemoteUser(userId : number) { private pushVideoToRemoteUser(userId: number, streamResult: LocalStreamStoreValue) {
try { try {
const PeerConnection = this.PeerConnectionArray.get(userId); const PeerConnection = this.PeerConnectionArray.get(userId);
if (!PeerConnection) { if (!PeerConnection) {
throw new Error('While adding media, cannot find user with ID ' + userId); throw new Error('While adding media, cannot find user with ID ' + userId);
} }
const result = get(localStreamStore); PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...streamResult.constraints})));
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints}))); if (streamResult.type === 'error') {
if (result.type === 'error') {
return; return;
} }
const localStream: MediaStream | null = result.stream; const localStream: MediaStream | null = streamResult.stream;
if(!localStream){ if(!localStream){
return; return;
@ -370,15 +413,11 @@ export class SimplePeer {
} }
} }
private pushScreenSharingToRemoteUser(userId : number) { private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) {
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
if (!PeerConnection) { if (!PeerConnection) {
throw new Error('While pushing screen sharing, cannot find user with ID ' + userId); throw new Error('While pushing screen sharing, cannot find user with ID ' + userId);
} }
const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture;
if(!localScreenCapture){
return;
}
for (const track of localScreenCapture.getTracks()) { for (const track of localScreenCapture.getTracks()) {
PeerConnection.addTrack(track, localScreenCapture); PeerConnection.addTrack(track, localScreenCapture);
@ -386,23 +425,18 @@ export class SimplePeer {
return; return;
} }
public sendLocalVideoStream(){ public sendLocalVideoStream(streamResult: LocalStreamStoreValue){
for (const user of this.Users) { for (const user of this.Users) {
this.pushVideoToRemoteUser(user.userId); this.pushVideoToRemoteUser(user.userId, streamResult);
} }
} }
/** /**
* Triggered locally when clicking on the screen sharing button * Triggered locally when clicking on the screen sharing button
*/ */
public sendLocalScreenSharingStream() { public sendLocalScreenSharingStream(localScreenCapture: MediaStream) {
if (!mediaManager.localScreenCapture) {
console.error('Could not find localScreenCapture to share')
return;
}
for (const user of this.Users) { for (const user of this.Users) {
this.sendLocalScreenSharingStreamToUser(user.userId); this.sendLocalScreenSharingStreamToUser(user.userId, localScreenCapture);
} }
} }
@ -415,11 +449,11 @@ export class SimplePeer {
} }
} }
private sendLocalScreenSharingStreamToUser(userId: number): void { private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void {
if (blackListManager.isBlackListed(userId)) return; if (blackListManager.isBlackListed(userId)) return;
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
if (this.PeerScreenSharingConnectionArray.has(userId)) { if (this.PeerScreenSharingConnectionArray.has(userId)) {
this.pushScreenSharingToRemoteUser(userId); this.pushScreenSharingToRemoteUser(userId, localScreenCapture);
return; return;
} }
@ -427,7 +461,7 @@ export class SimplePeer {
userId, userId,
initiator: true initiator: true
}; };
const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser); const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser, localScreenCapture);
if (!PeerConnectionScreenSharing) { if (!PeerConnectionScreenSharing) {
return; return;
} }

View File

@ -27,7 +27,7 @@ export class VideoPeer extends Peer {
private onBlockSubscribe: Subscription; private onBlockSubscribe: Subscription;
private onUnBlockSubscribe: Subscription; private onUnBlockSubscribe: Subscription;
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, localStream: MediaStream | null) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
//reconnectTimer: 10000, //reconnectTimer: 10000,
@ -107,7 +107,7 @@ export class VideoPeer extends Peer {
this._onFinish(); this._onFinish();
}); });
this.pushVideoToRemoteUser(); this.pushVideoToRemoteUser(localStream);
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => { this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
if (userId === this.userId) { if (userId === this.userId) {
this.toggleRemoteStream(false); this.toggleRemoteStream(false);
@ -190,9 +190,8 @@ export class VideoPeer extends Peer {
} }
} }
private pushVideoToRemoteUser() { private pushVideoToRemoteUser(localStream: MediaStream | null) {
try { try {
const localStream: MediaStream | null = mediaManager.localStream;
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)}))); this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)})));
if(!localStream){ if(!localStream){

View File

@ -1,9 +1,24 @@
@media (hover: none) {
/**
* If we cannot hover over elements, let's display camera button in full.
*/
.btn-cam-action {
div {
transform: translateY(0px);
}
}
}
@media screen and (max-width: 700px), @media screen and (max-width: 700px),
screen and (max-height: 700px){ screen and (max-height: 700px){
video#myCamVideo { video.myCamVideo {
width: 150px; width: 150px;
} }
.div-myCamVideo.hide {
right: -160px;
}
.sidebar { .sidebar {
width: 20%; width: 20%;
min-width: 200px; min-width: 200px;
@ -22,21 +37,6 @@
} }
} }
.btn-cam-action {
min-width: 150px;
&:hover{
transform: translateY(20px);
}
div {
margin: 0 1%;
&:hover {
background-color: #666;
}
margin-bottom: 30px;
}
}
.main-section { .main-section {
position: absolute; position: absolute;
width: 100%; width: 100%;

View File

@ -42,6 +42,10 @@ body .message-info.warning {
height: 100%; height: 100%;
} }
.video-container.nes-container.is-dark {
padding: 12px 12px !important;
}
.video-container i { .video-container i {
position: absolute; position: absolute;
width: 100px; width: 100px;
@ -133,23 +137,23 @@ body .message-info.warning {
outline: none; outline: none;
} }
.video-container#div-myCamVideo { .video-container.div-myCamVideo{
padding: 12px 12px !important; border: none;
} }
#div-myCamVideo { .div-myCamVideo {
position: absolute; position: absolute !important;
right: 15px; right: 15px;
bottom: 30px; bottom: 30px;
max-height: 20%; max-height: 20%;
transition: right 350ms; transition: right 350ms;
} }
#div-myCamVideo.hide { .div-myCamVideo.hide {
right: -20vw; right: -20vw;
} }
video#myCamVideo { video.myCamVideo{
width: 15vw; width: 15vw;
-webkit-transform: scaleX(-1); -webkit-transform: scaleX(-1);
transform: scaleX(-1); transform: scaleX(-1);
@ -219,7 +223,9 @@ video#myCamVideo {
/*btn animation*/ /*btn animation*/
.btn-cam-action div{ .btn-cam-action div{
/*position: absolute;*/ display: flex;
align-items: center;
justify-content: center;
width: 44px; width: 44px;
height: 44px; height: 44px;
width: auto; width: auto;
@ -278,7 +284,7 @@ video#myCamVideo {
.btn-cam-action div img { .btn-cam-action div img {
height: 22px; height: 22px;
width: auto; width: 30px;
position: relative; position: relative;
pointer-events: all; pointer-events: all;
} }
@ -1299,4 +1305,5 @@ div.action.danger p.action-body {
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
z-index: 999;
} }