Merge remote-tracking branch 'upstream/develop' into next

This commit is contained in:
Thomas Basler 2021-06-03 15:22:35 +02:00
commit 1797d8f7cc
78 changed files with 1764 additions and 1975 deletions

View File

@ -22,6 +22,10 @@
- Mouse wheel support to zoom in / out - Mouse wheel support to zoom in / out
- Pinch support on mobile to zoom in / out - Pinch support on mobile to zoom in / out
- Improved virtual joystick size (adapts to the zoom level) - Improved virtual joystick size (adapts to the zoom level)
- Redesigned intermediate scenes
- Redesigned Select Companion scene
- Redesigned Enter Your Name scene
- Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use
- New scripting API features: - New scripting API features:
- Use `WA.loadSound(): Sound` to load / play / stop a sound - Use `WA.loadSound(): Sound` to load / play / stop a sound

View File

@ -108,17 +108,10 @@
</label> </label>
</div> </div>
</div> </div>
<div class="audio-playing">
<img src="/resources/logos/megaphone.svg" />
</div>
</div> </div>
<div id="activeScreenSharing" class="active-screen-sharing active"> <div id="activeScreenSharing" class="active-screen-sharing active">
</div> </div>
<div id="webRtcSetup" class="webrtcsetup">
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
<video id="myCamVideoSetup" autoplay muted></video>
</div>
<audio id="audio-webrtc-in"> <audio id="audio-webrtc-in">
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3"> <source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
</audio> </audio>

View File

@ -1,160 +0,0 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#customizeScene {
background: #0000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 10px auto 0;
color: #ebeeee;
width: 42vw;
height: 48vh;
/*max-width: 300px;
max-height: 48vh;*/
overflow: hidden;
}
#customizeScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#customizeScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#customizeScene section {
margin: 10px;
}
#customizeScene section.action {
text-align: center;
position: sticky;
bottom: 0;
top: 100%;
}
#customizeScene section.action.action-move {
top: 55%;
}
#customizeScene button {
margin: 2px 10px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
}
#customizeScene button#customizeSceneFormCancel {
background-color: #aca6a600;
color: #292929;
}
#customizeScene section h6,
#customizeScene section h5{
margin: 1px;
}
#customizeScene section.text-center{
text-align: center;
}
#customizeScene section a{
font-size: 14px;
text-decoration: underline;
color: #ebeeee;
}
#customizeScene section a:hover{
font-weight: 700;
}
#customizeScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#customizeScene section p.err{
color: red;
text-align: center;
}
#customizeScene section p.info{
display: none;
text-align: center;
}
#customizeScene section input#customizeSceneLink{
background-color: #a1a3a3;
}
#customizeScene section button.customizeSceneButton{
position: absolute;
margin: 0;
top: -8vh;
font-size: 10px;
padding: 2px 4px;
}
#customizeScene section button.customizeSceneButton#customizeSceneButtonLeft{
left: 0vw;
}
#customizeScene section button.customizeSceneButton#customizeSceneButtonRight{
right: 0vw;
}
#customizeScene section button.customizeSceneButton#customizeSceneButtonUp{
left: calc(2vw + 40px);
transform: rotate(90deg);
margin-top: -20px;
}
#customizeScene section button.customizeSceneButton#customizeSceneButtonDown{
right: calc(2vw + 40px);
transform: rotate(90deg);
margin-top: 20px;
}
#customizeScene section.action img{
width: 8px;
height: 8px;
}
#customizeScene section.action a#customizeSceneFormBack img{
margin-top: -2px;
}
#customizeScene section.action button#customizeSceneFormSubmit img{
transform: rotate(180deg);
}
@media only screen and (max-width: 600px) {
#customizeScene {
max-width: 160px;
overflow-y: scroll;
}
}
@media only screen and (max-height: 400px) {
#customizeScene section.action {
top: 92%;
}
#customizeScene section.action.action-move {
top: 80%;
}
}
</style>
<form id="customizeScene" hidden>
<section class="text-center">
<h5>Custom your WOKA</h3>
</section>
<section class="action action-move">
<button class="customizeSceneButton" id="customizeSceneButtonLeft"> < </button>
<button class="customizeSceneButton" id="customizeSceneButtonRight"> > </button>
<!--<button class="customizeSceneButton" id="customizeSceneButtonUp"> < </button>
<button class="customizeSceneButton" id="customizeSceneButtonDown"> > </button>-->
</section>
<section class="action">
<a type="submit" id="customizeSceneFormBack">Back <img src="resources/objects/arrow_up.png"/></a>
<button type="submit" id="customizeSceneFormSubmit">Next <img src="resources/objects/arrow_up.png"/></button>
</section>
</form>

View File

@ -1,129 +0,0 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#enableCameraScene {
background: #000000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 20px auto 0;
color: #ebeeee;
max-height: 48vh;
width: 42vw;
max-width: 300px;
overflow: hidden;
}
#enableCameraScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#enableCameraScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#enableCameraScene section.title {
position: absolute;
top: 1vh;
width: 100%;
}
#enableCameraScene section.action{
text-align: center;
margin: 0;
position: absolute;
top: 40vh;
width: 100%;
}
#enableCameraScene button {
margin: 10px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
}
#enableCameraScene button#enableCameraSceneFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#enableCameraScene section h6,
#enableCameraScene section h5{
margin: 1px;
}
#enableCameraScene section.text-center{
text-align: center;
}
#enableCameraScene section a{
font-size: 8px;
text-decoration: underline;
color: #ebeeee;
}
#enableCameraScene section a:hover{
font-weight: 700;
}
#enableCameraScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#enableCameraScene section p.err{
color: red;
text-align: center;
}
#enableCameraScene section p.info{
display: none;
text-align: center;
}
#enableCameraScene section input#enableCameraSceneLink{
background-color: #a1a3a3;
}
#enableCameraScene section img{
width: 160px;
margin: 20px 0;
}
/*@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#enableCameraScene{
overflow-y: scroll;
}
}*/
</style>
<form id="enableCameraScene" hidden>
<!-- FIX me -->
<section class="title text-center">
<h5>Turn on your camera and microphone</h5>
</section>
<!--<section class="text-center">
<video id="myCamVideoSetup" autoplay muted></video>
</section>
<section class="text-center">
<h5>Select your camera</h3>
<select id="camera">
</select>
</section>
<section class="text-center">
<h5>Select your michrophone</h3>
<select id="michrophone">
</select>
</section>-->
<section class="action">
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
</section>
</form>

View File

@ -1,134 +0,0 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#selectCompanionScene {
background: #0000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 10px auto 0;
color: #ebeeee;
max-height: 40vh;
max-width: 300px;
width: 40vw;
overflow: hidden;
}
#selectCompanionScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#selectCompanionScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#selectCompanionScene section {
margin: 10px;
}
#selectCompanionScene section.action {
text-align: center;
margin: 0;
margin-top: 20vh;
}
#selectCompanionScene button {
margin: 10px 4px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
width: 100px;
}
#selectCompanionScene button#selectCompanionSceneFormCancel {
background-color: #aca6a600;
color: #292929;
}
#selectCompanionScene section h6,
#selectCompanionScene section h5{
margin: 1px;
}
#selectCompanionScene section.text-center{
text-align: center;
}
#selectCompanionScene section a{
font-size: 14px;
text-decoration: underline;
color: #ebeeee;
}
#selectCompanionScene section a:hover{
font-weight: 700;
}
#selectCompanionScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#selectCompanionScene section p.err{
color: red;
text-align: center;
}
#selectCompanionScene section p.info{
display: none;
text-align: center;
}
#selectCompanionScene section input#selectCompanionSceneLink{
background-color: #a1a3a3;
}
#selectCompanionScene section img{
width: 160px;
margin: 20px 0;
}
#selectCompanionScene section button.selectCharacterButton{
position: absolute;
top: 20vh;
margin: 0;
width: 25px;
}
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonLeft{
left: 1vw;
}
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonRight{
right: 1vw;
}
#selectCompanionScene section button#selectCompanionSceneFormCustomYourOwnSubmit{
font-size: 14px;
width: auto;
margin-top: -2px;
background-color: #ffd700;
color: black;
}
@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#selectCompanionScene{
overflow-y: scroll;
}
}
</style>
<form id="selectCompanionScene" hidden>
<section class="text-center">
<h5>Select your WOKA</h5>
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
</section>
<section class="action">
<a herf="#" id="selectCompanionSceneFormBack">No companion</a>
<button type="submit" id="selectCompanionSceneFormSubmit">Continue</button>
</section>
</form>

View File

@ -1,7 +1,4 @@
<style> <style>
*{
font-family: PixelFont-7,monospace!important;
}
#gameMenu main{ #gameMenu main{
margin-top: 15px; margin-top: 15px;
} }

View File

@ -1,10 +1,4 @@
<style> <style>
*{
font-family: PixelFont-7,monospace!important;
}
#menuIcon {
pointer-events: auto;
}
#menuIcon button { #menuIcon button {
background-color: black; background-color: black;
color: white; color: white;

View File

@ -1,7 +1,4 @@
<style> <style>
*{
font-family: PixelFont-7,monospace!important;
}
#gameQuality { #gameQuality {
background: #eceeee; background: #eceeee;
border: 1px solid #42464b; border: 1px solid #42464b;

View File

@ -1,7 +1,4 @@
<style> <style>
*{
font-family: PixelFont-7,monospace!important;
}
#gameReport { #gameReport {
background: #eceeee; background: #eceeee;
border: 1px solid #42464b; border: 1px solid #42464b;

View File

@ -1,7 +1,4 @@
<style> <style>
*{
font-family: PixelFont-7,monospace!important;
}
#gameShare { #gameShare {
background: #eceeee; background: #eceeee;
border: 1px solid #42464b; border: 1px solid #42464b;

View File

@ -1,109 +0,0 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#helpCameraSettings {
background: #eceeee;
border: 1px solid #42464b;
border-radius: 6px;
margin: 25px auto 0;
width: 400px;
max-height: calc(48vh - 50px);
max-width: 48vw;
overflow: hidden;
overflow-y: scroll;
}
#helpCameraSettings h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#helpCameraSettings section {
margin: 10px;
}
#helpCameraSettings section.action{
text-align: center;
margin: 0;
}
#helpCameraSettings button {
margin: 10px 4px;
background-color: black;
color: white;
border-radius: 7px;
padding-bottom: 4px;
}
#helpCameraSettings button#helpCameraSettingsFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#helpCameraSettings section a{
font-size: 12px;
text-decoration: underline;
color: black;
}
#helpCameraSettings section h6,
#helpCameraSettings section h5{
margin: 1px;
}
#helpCameraSettings section.text-center{
text-align: center;
}
#helpCameraSettings section p{
font-size: 8px;
margin: 0px 20px;
}
#helpCameraSettings section p a{
font-size: 8px;
}
#helpCameraSettings section p.err{
color: #ff0000;
}
#helpCameraSettings section ul{
margin: 6px;
}
#helpCameraSettings section li{
text-align: left;
font-size: 8px;
}
#helpCameraSettings section img {
width: 200px;
margin-top: 10px;
}
@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#helpCameraSettings{
overflow-y: scroll;
}
}
</style>
<form id="helpCameraSettings" hidden>
<section class="text-center">
<h5>Camera/Microphone access needed</h5>
<p class="err" id="permissionError">Permission denied</p>
<p class="info">You must allow camera and microphone access in your browser.</p>
<ul>
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
</ul>
<p class="info">Once you've followed these steps, please refresh this page.</p>
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
<p id='browserHelpSetting'></p>
</section>
<!--<section class="text-center">
<p>If your problem persist, please contact us: <a id="mailto" href="mailto:workadventure@thecodingmachine.com?subject=Support camera and microphone settings" target="_blank"> workadventure@thecodingmachine.com</a>.</p>
</section>-->
</section>
<section class="action">
<a href="#" id="helpCameraSettingsFormRefresh">Refresh</a>
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
</section>
</form>

View File

@ -1,120 +0,0 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#loginScene {
background: #000000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 20px auto 0;
width: 90%;
max-width: 200px;
color: #ebeeee;
max-height: 40vh;
overflow: hidden;
}
#loginScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#loginScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#loginScene section {
margin: 10px;
}
#loginScene section.action{
text-align: center;
margin: 0;
}
#loginScene button {
margin: 10px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
width: 100px;
}
#loginScene button#loginSceneFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#loginScene section h6,
#loginScene section h5{
margin: 1px;
}
#loginScene section.text-center{
text-align: center;
}
#loginScene section a{
font-size: 8px;
text-decoration: underline;
color: #ebeeee;
}
#loginScene section a:hover{
font-weight: 700;
}
#loginScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#loginScene section p.err{
color: red;
text-align: center;
}
#loginScene section p.info{
display: none;
text-align: center;
}
#loginScene section input#loginSceneLink{
background-color: #a1a3a3;
}
#loginScene section img{
width: 160px;
margin: 20px 0;
}
@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#loginScene{
overflow-y: scroll;
}
}
</style>
<form id="loginScene" hidden>
<section class="text-center">
<img src="resources/logos/logo.png">
</section>
<section class="text-center">
<h5>Enter your name</h5>
<p class="info">9 chars maximum</p>
<p class="err" id="errorLoginScene"></p>
</section>
<section>
<input type="text" name="email" id="loginSceneName">
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
</section>
<section class="action">
<button type="submit" id="loginSceneFormSubmit">Continue</button>
</section>
</form>

View File

@ -1,142 +0,0 @@
<style>
*{
font-family: PixelFont-7,monospace!important;
}
#selectCharacterScene {
background: #0000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 10px auto 0;
color: #ebeeee;
max-height: 48vh;
max-width: 300px;
width: 40vw;
overflow: hidden;
}
#selectCharacterScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#selectCharacterScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#selectCharacterScene section {
margin: 10px;
}
#selectCharacterScene section.action {
text-align: center;
margin: 0;
margin-top: 28vh;
}
#selectCharacterScene button {
margin: 10px 0px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
width: 100px;
}
#selectCharacterScene button#selectCharacterSceneFormCancel {
background-color: #aca6a600;
color: #292929;
}
#selectCharacterScene section h6,
#selectCharacterScene section h5{
margin: 1px;
}
#selectCharacterScene section.text-center{
text-align: center;
}
#selectCharacterScene section a{
font-size: 8px;
text-decoration: underline;
color: #ebeeee;
}
#selectCharacterScene section a:hover{
font-weight: 700;
}
#selectCharacterScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#selectCharacterScene section p.err{
color: red;
text-align: center;
}
#selectCharacterScene section p.info{
display: none;
text-align: center;
}
#selectCharacterScene section input#selectCharacterSceneLink{
background-color: #a1a3a3;
}
#selectCharacterScene section img{
width: 160px;
margin: 20px 0;
}
#selectCharacterScene section button.selectCharacterButton{
position: absolute;
top: 20vh;
margin: 0;
width: 25px;
}
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
display: none;
left: 1vw;
}
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
display: none;
right: 1vw;
}
#selectCharacterScene section button#selectCharacterSceneFormCustomYourOwnSubmit{
font-size: 14px;
width: auto;
margin-top: -2px;
background-color: #ffd700;
color: black;
}
@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#selectCharacterScene{
overflow-y: scroll;
}
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
display: block;
}
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
display: block;
}
}
</style>
<form id="selectCharacterScene" hidden>
<section class="text-center">
<h5>Select your WOKA</h5>
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
</section>
<section class="action">
<button type="submit" id="selectCharacterSceneFormSubmit">Continue</button>
<button type="submit" id="selectCharacterSceneFormCustomYourOwnSubmit">Custom your WOKA</button>
</section>
</form>

View File

@ -1,7 +1,4 @@
<style> <style>
*{
font-family: PixelFont-7,monospace!important;
}
#warningMain { #warningMain {
border-radius: 5px; border-radius: 5px;
height: 100px; height: 100px;

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -48,7 +48,8 @@
"quill": "1.3.6", "quill": "1.3.6",
"rxjs": "^6.6.3", "rxjs": "^6.6.3",
"simple-peer": "^9.6.2", "simple-peer": "^9.6.2",
"socket.io-client": "^2.3.0" "socket.io-client": "^2.3.0",
"standardized-audio-context": "^25.2.4"
}, },
"scripts": { "scripts": {
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",

View File

@ -3,6 +3,8 @@ import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
import type {RoomConnection} from "../Connexion/RoomConnection"; import type {RoomConnection} from "../Connexion/RoomConnection";
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels"; import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
import {soundManager} from "../Phaser/Game/SoundManager";
export class GlobalMessageManager { export class GlobalMessageManager {
@ -43,45 +45,8 @@ export class GlobalMessageManager {
} }
} }
private playAudioMessage(messageId : string, urlMessage: string){ private playAudioMessage(messageId : string, urlMessage: string) {
//delete previous elements soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
const previousDivAudio = document.getElementsByClassName('audio-playing');
for(let i = 0; i < previousDivAudio.length; i++){
previousDivAudio[i].remove();
}
//create new element
const divAudio : HTMLDivElement = document.createElement('div');
divAudio.id = `audio-playing-${messageId}`;
divAudio.classList.add('audio-playing');
const imgAudio : HTMLImageElement = document.createElement('img');
imgAudio.src = '/resources/logos/megaphone.svg';
const pAudio : HTMLParagraphElement = document.createElement('p');
pAudio.textContent = 'Message audio'
divAudio.appendChild(imgAudio);
divAudio.appendChild(pAudio);
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
mainSectionDiv.appendChild(divAudio);
const messageAudio : HTMLAudioElement = document.createElement('audio');
messageAudio.id = this.getHtmlMessageId(messageId);
messageAudio.autoplay = true;
messageAudio.style.display = 'none';
messageAudio.onended = () => {
divAudio.classList.remove('active');
messageAudio.remove();
setTimeout(() => {
divAudio.remove();
}, 1000);
}
messageAudio.onplay = () => {
divAudio.classList.add('active');
}
const messageAudioSource : HTMLSourceElement = document.createElement('source');
messageAudioSource.src = `${UPLOADER_URL}${urlMessage}`;
messageAudio.appendChild(messageAudioSource);
mainSectionDiv.appendChild(messageAudio);
} }
private playTextMessage(messageId : string, htmlMessage: string){ private playTextMessage(messageId : string, htmlMessage: string){

View File

@ -1,18 +1,76 @@
<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 {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
import CameraControls from "./CameraControls.svelte"; import CameraControls from "./CameraControls.svelte";
import MyCamera from "./MyCamera.svelte"; import MyCamera from "./MyCamera.svelte";
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
import {selectCompanionSceneVisibleStore} from "../Stores/SelectCompanionStore";
import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore";
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore";
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
import LoginScene from "./Login/LoginScene.svelte";
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
import {Game} from "../Phaser/Game/Game";
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
import AudioPlaying from "./UI/AudioPlaying.svelte";
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
export let game: Game;
</script> </script>
<div> <div>
{#if $loginSceneVisibleStore}
<div class="scrollable">
<LoginScene game={game}></LoginScene>
</div>
{/if}
{#if $selectCharacterSceneVisibleStore}
<div>
<SelectCharacterScene game={ game }></SelectCharacterScene>
</div>
{/if}
{#if $customCharacterSceneVisibleStore}
<div>
<CustomCharacterScene game={ game }></CustomCharacterScene>
</div>
{/if}
{#if $selectCompanionSceneVisibleStore}
<div>
<SelectCompanionScene game={ game }></SelectCompanionScene>
</div>
{/if}
{#if $enableCameraSceneVisibilityStore}
<div class="scrollable">
<EnableCameraScene game={game}></EnableCameraScene>
</div>
{/if}
{#if $soundPlayingStore}
<div>
<AudioPlaying url={$soundPlayingStore} />
</div>
{/if}
<!--
{#if $menuIconVisible}
<div>
<MenuIcon />
</div>
{/if}
-->
{#if $gameOverlayVisibilityStore} {#if $gameOverlayVisibilityStore}
<!-- {#if $menuIconVisible} <div>
<MenuIcon /> <MyCamera></MyCamera>
{/if} --> <CameraControls></CameraControls>
<MyCamera></MyCamera> </div>
<CameraControls></CameraControls> {/if}
{#if $helpCameraSettingsVisibleStore}
<div>
<HelpCameraSettingsPopup game={ game }></HelpCameraSettingsPopup>
</div>
{/if} {/if}
</div> </div>

View File

@ -34,26 +34,28 @@
</script> </script>
<div class="btn-cam-action"> <div>
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore}> <div class="btn-cam-action">
{#if $requestedScreenSharingState} <div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
<img src={monitorImg} alt="Start screen sharing"> {#if $requestedScreenSharingState}
{:else} <img src={monitorImg} alt="Start screen sharing">
<img src={monitorCloseImg} alt="Stop screen sharing"> {:else}
{/if} <img src={monitorCloseImg} alt="Stop screen sharing">
</div> {/if}
<div class="btn-video" on:click={cameraClick}> </div>
{#if $requestedCameraState} <div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}>
<img src={cinemaImg} alt="Turn on webcam"> {#if $requestedCameraState}
{:else} <img src={cinemaImg} alt="Turn on webcam">
<img src={cinemaCloseImg} alt="Turn off webcam"> {:else}
{/if} <img src={cinemaCloseImg} alt="Turn off webcam">
</div> {/if}
<div class="btn-micro" on:click={microphoneClick}> </div>
{#if $requestedMicrophoneState} <div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
<img src={microphoneImg} alt="Turn on microphone"> {#if $requestedMicrophoneState}
{:else} <img src={microphoneImg} alt="Turn on microphone">
<img src={microphoneCloseImg} alt="Turn off microphone"> {:else}
{/if} <img src={microphoneCloseImg} alt="Turn off microphone">
{/if}
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,119 @@
<script lang="typescript">
import { Game } from "../../Phaser/Game/Game";
import { CustomizeSceneName } from "../../Phaser/Login/CustomizeScene";
export let game: Game;
const customCharacterScene = game.scene.getScene(CustomizeSceneName);
let activeRow = customCharacterScene.activeRow;
function selectLeft() {
customCharacterScene.moveCursorHorizontally(-1);
}
function selectRight() {
customCharacterScene.moveCursorHorizontally(1);
}
function selectUp() {
customCharacterScene.moveCursorVertically(-1);
activeRow = customCharacterScene.activeRow;
}
function selectDown() {
customCharacterScene.moveCursorVertically(1);
activeRow = customCharacterScene.activeRow;
}
function previousScene() {
customCharacterScene.backToPreviousScene();
}
function finish() {
customCharacterScene.nextSceneToCamera();
}
</script>
<form class="customCharacterScene">
<section class="text-center">
<h2>Customize your WOKA</h2>
</section>
<section class="action action-move">
<button class="customCharacterSceneButton customCharacterSceneButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> &lt; </button>
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> &gt; </button>
</section>
<section class="action">
{#if activeRow === 0}
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
{/if}
{#if activeRow !== 0}
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
{/if}
{#if activeRow === 5}
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
{/if}
{#if activeRow !== 5}
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
{/if}
</section>
</form>
<style lang="scss">
form.customCharacterScene {
font-family: "Press Start 2P";
pointer-events: auto;
color: #ebeeee;
section {
margin: 10px;
&.action {
text-align: center;
margin-top: 55vh;
}
h2 {
font-family: "Press Start 2P";
margin: 1px;
}
&.text-center {
text-align: center;
}
button.customCharacterSceneButton {
position: absolute;
top: 33vh;
margin: 0;
}
button.customCharacterSceneFormBack {
color: #292929;
}
}
button {
font-family: "Press Start 2P";
&.customCharacterSceneButtonLeft {
left: 33vw;
}
&.customCharacterSceneButtonRight {
right: 33vw;
}
}
}
@media only screen and (max-width: 800px) {
form.customCharacterScene button.customCharacterSceneButtonLeft{
left: 5vw;
}
form.customCharacterScene button.customCharacterSceneButtonRight{
right: 5vw;
}
}
</style>

View File

@ -0,0 +1,217 @@
<script lang="typescript">
import {Game} from "../../Phaser/Game/Game";
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
import {
audioConstraintStore,
cameraListStore,
localStreamStore,
microphoneListStore,
videoConstraintStore
} from "../../Stores/MediaStore";
import {onDestroy} from "svelte";
import HorizontalSoundMeterWidget from "./HorizontalSoundMeterWidget.svelte";
import cinemaCloseImg from "../images/cinema-close.svg";
import cinemaImg from "../images/cinema.svg";
import microphoneImg from "../images/microphone.svg";
export let game: Game;
let selectedCamera : string|null = null;
let selectedMicrophone : string|null = null;
const enableCameraScene = game.scene.getScene(EnableCameraSceneName) as EnableCameraScene;
function submit() {
enableCameraScene.login();
}
function srcObject(node, stream) {
node.srcObject = stream;
return {
update(newStream) {
if (node.srcObject != newStream) {
node.srcObject = newStream
}
}
}
}
let stream: MediaStream | null;
const unsubscribe = localStreamStore.subscribe(value => {
if (value.type === 'success') {
stream = value.stream;
if (stream !== null) {
const videoTracks = stream.getVideoTracks();
if (videoTracks.length > 0) {
selectedCamera = videoTracks[0].getSettings().deviceId;
}
const audioTracks = stream.getAudioTracks();
if (audioTracks.length > 0) {
selectedMicrophone = audioTracks[0].getSettings().deviceId;
}
}
} else {
stream = null;
selectedCamera = null;
selectedMicrophone = null;
}
});
onDestroy(unsubscribe);
function normalizeDeviceName(label: string): string {
// remove text in parenthesis
return label.replace(/\([^()]*\)/g, '').trim();
}
function selectCamera() {
videoConstraintStore.setDeviceId(selectedCamera);
}
function selectMicrophone() {
audioConstraintStore.setDeviceId(selectedMicrophone);
}
</script>
<form class="enableCameraScene" on:submit|preventDefault={submit}>
<section class="text-center">
<h2>Turn on your camera and microphone</h2>
</section>
{#if $localStreamStore.stream}
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
{:else }
<div class="webrtcsetup">
<img class="background-img" src={cinemaCloseImg} alt="">
</div>
{/if}
<HorizontalSoundMeterWidget stream={stream}></HorizontalSoundMeterWidget>
<section class="selectWebcamForm">
{#if $cameraListStore.length > 1 }
<div class="control-group">
<img src={cinemaImg} alt="Camera" />
<div class="nes-select">
<select bind:value={selectedCamera} on:change={selectCamera}>
{#each $cameraListStore as camera}
<option value={camera.deviceId}>
{normalizeDeviceName(camera.label)}
</option>
{/each}
</select>
</div>
</div>
{/if}
{#if $microphoneListStore.length > 1 }
<div class="control-group">
<img src={microphoneImg} alt="Microphone" />
<div class="nes-select">
<select bind:value={selectedMicrophone} on:change={selectMicrophone}>
{#each $microphoneListStore as microphone}
<option value={microphone.deviceId}>
{normalizeDeviceName(microphone.label)}
</option>
{/each}
</select>
</div>
</div>
{/if}
</section>
<section class="action">
<button type="submit" class="nes-btn is-primary letsgo" >Let's go!</button>
</section>
</form>
<style lang="scss">
.enableCameraScene {
pointer-events: auto;
margin: 20px auto 0;
color: #ebeeee;
section.selectWebcamForm {
margin-top: 3vh;
margin-bottom: 3vh;
min-height: 10vh;
width: 50%;
margin-left: auto;
margin-right: auto;
select {
font-family: "Press Start 2P";
margin-top: 1vh;
margin-bottom: 1vh;
option {
font-family: "Press Start 2P";
}
}
}
section.action{
text-align: center;
margin: 0;
width: 100%;
}
h2{
font-family: "Press Start 2P";
margin: 1px;
}
section.text-center{
text-align: center;
}
button.letsgo {
font-size: 200%;
}
.control-group {
display: flex;
flex-direction: row;
img {
width: 30px;
margin-right: 10px;
}
}
.webrtcsetup{
margin-top: 2vh;
margin-left: auto;
margin-right: auto;
height: 28.125vw;
width: 50vw;
border: white 6px solid;
display: flex;
align-items: center;
justify-content: center;
img.background-img {
width: 40%;
}
}
.myCamVideoSetup {
margin-top: 2vh;
margin-left: auto;
margin-right: auto;
max-height: 50vh;
width: 50vw;
border: white 6px solid;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,82 @@
<script lang="typescript">
import { AudioContext } from 'standardized-audio-context';
import {SoundMeter} from "../../Phaser/Components/SoundMeter";
import {onDestroy} from "svelte";
export let stream: MediaStream | null;
let volume = 0;
const NB_BARS = 20;
let timeout;
const soundMeter = new SoundMeter();
let display = false;
$: {
if (stream && stream.getAudioTracks().length > 0) {
display = true;
soundMeter.connectToSource(stream, new AudioContext());
if (timeout) {
clearInterval(timeout);
}
timeout = setInterval(() => {
try{
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
//console.log(volume);
}catch(err){
}
}, 100);
} else {
display = false;
}
}
onDestroy(() => {
soundMeter.stop();
if (timeout) {
clearInterval(timeout);
}
})
function color(i: number, volume: number) {
const red = 255 * i / NB_BARS;
const green = 255 * (1 - i / NB_BARS);
let alpha = 1;
if (i >= volume) {
alpha = 0.5;
}
return 'background-color:rgba('+red+', '+green+', 0, '+alpha+')';
}
</script>
<div class="horizontal-sound-meter" class:active={display}>
{#each [...Array(NB_BARS).keys()] as i}
<div style={color(i, volume)}></div>
{/each}
</div>
<style lang="scss">
.horizontal-sound-meter {
display: flex;
flex-direction: row;
width: 50%;
height: 30px;
margin-left: auto;
margin-right: auto;
margin-top: 1vh;
}
.horizontal-sound-meter div {
margin-left: 5px;
flex-grow: 1;
}
</style>

View File

@ -0,0 +1,73 @@
<script lang="typescript">
import { fly } from 'svelte/transition';
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
let isAndroid = window.navigator.userAgent.includes('Android');
let isFirefox = window.navigator.userAgent.includes('Firefox');
let isChrome = window.navigator.userAgent.includes('Chrome');
function refresh() {
window.location.reload();
}
function close() {
helpCameraSettingsVisibleStore.set(false);
}
</script>
<form class="helpCameraSettings nes-container" on:submit|preventDefault={close} transition:fly="{{ y: -900, duration: 500 }}">
<section>
<h2>Camera / Microphone access needed</h2>
<p class="err">Permission denied</p>
<p>You must allow camera and microphone access in your browser.</p>
<p>
{#if isFirefox }
<p class="err">Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.</p>
<img src={firefoxImg} alt="" />
{:else if isChrome && !isAndroid }
<img src={chromeImg} alt="" />
{/if}
</p>
</section>
<section>
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}>Refresh</button>
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}>Continue without webcam</button>
</section>
</form>
<style lang="scss">
.helpCameraSettings {
pointer-events: auto;
background: #eceeee;
margin-left: auto;
margin-right: auto;
margin-top: 10vh;
max-height: 80vh;
max-width: 80vw;
overflow: auto;
text-align: center;
h2 {
font-family: 'Press Start 2P';
}
section {
p {
margin: 15px;
font-family: 'Press Start 2P';
& .err {
color: #ff0000;
}
}
img {
max-width: 500px;
width: 100%;
}
}
}
</style>

View File

@ -0,0 +1,123 @@
<script lang="typescript">
import {Game} from "../../Phaser/Game/Game";
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
import {DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH} from "../../Enum/EnvironmentVariable";
import logoImg from "../images/logo.png";
import {gameManager} from "../../Phaser/Game/GameManager";
import {maxUserNameLength} from "../../Connexion/LocalUser";
export let game: Game;
const loginScene = game.scene.getScene(LoginSceneName);
let name = gameManager.getPlayerName() || '';
let startValidating = false;
function submit() {
startValidating = true;
let finalName = name.trim();
if (finalName !== '') {
loginScene.login(finalName);
}
}
</script>
<form class="loginScene" on:submit|preventDefault={submit}>
<section class="text-center">
<img src={logoImg} alt="WorkAdventure logo" />
</section>
<section class="text-center">
<h2>Enter your name</h2>
</section>
<input type="text" name="loginSceneName" class="nes-input is-dark" autofocus maxlength={MAX_USERNAME_LENGTH} bind:value={name} on:keypress={() => {startValidating = true}} class:is-error={name.trim() === '' && startValidating} />
<section class="error-section">
{#if name.trim() === '' && startValidating }
<p class="err">The name is empty</p>
{/if}
</section>
{#if DISPLAY_TERMS_OF_USE}
<section class="terms-and-conditions">
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
</section>
{/if}
<section class="action">
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
</section>
</form>
<style lang="scss">
.loginScene {
pointer-events: auto;
margin: 20px auto 0;
width: 90%;
color: #ebeeee;
display: flex;
flex-flow: column wrap;
align-items: center;
input {
text-align: center;
font-family: "Press Start 2P";
max-width: 400px;
}
.terms-and-conditions {
max-width: 400px;
}
p.err {
color: #ce372b;
text-align: center;
}
section {
margin: 10px;
&.error-section {
min-height: 2rem;
margin: 0;
p {
margin: 0;
}
}
&.action {
text-align: center;
margin-top: 20px;
}
h2 {
font-family: "Press Start 2P";
margin: 1px;
}
&.text-center {
text-align: center;
}
a {
text-decoration: underline;
color: #ebeeee;
}
a:hover {
font-weight: 700;
}
p {
text-align: left;
margin: 10px 10px;
}
img {
width: 100%;
margin: 20px 0;
}
}
}
</style>

View File

@ -38,7 +38,7 @@
<div> <div>
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}> <div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted></video> <video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
<!-- {#if stream} <!-- {#if stream}
<SoundMeterWidget stream={stream}></SoundMeterWidget> <SoundMeterWidget stream={stream}></SoundMeterWidget>
{/if} --> {/if} -->

View File

@ -0,0 +1,87 @@
<script lang="typescript">
import {Game} from "../../Phaser/Game/Game";
import {SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
export let game: Game;
const selectCompanionScene = game.scene.getScene(SelectCompanionSceneName);
function selectLeft() {
selectCompanionScene.moveToLeft();
}
function selectRight() {
selectCompanionScene.moveToRight();
}
function noCompanion() {
selectCompanionScene.closeScene();
}
function selectCompanion() {
selectCompanionScene.selectCompanion();
}
</script>
<form class="selectCompanionScene">
<section class="text-center">
<h2>Select your companion</h2>
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}> &lt; </button>
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={selectRight}> &gt; </button>
</section>
<section class="action">
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}>No companion</button>
<button type="submit" class="selectCompanionSceneFormSubmit nes-btn is-primary" on:click|preventDefault={selectCompanion}>Continue</button>
</section>
</form>
<style lang="scss">
form.selectCompanionScene {
font-family: "Press Start 2P";
pointer-events: auto;
color: #ebeeee;
section {
margin: 10px;
&.action {
text-align: center;
margin-top: 55vh;
}
h2 {
font-family: "Press Start 2P";
margin: 1px;
}
&.text-center {
text-align: center;
}
button.selectCharacterButton {
position: absolute;
top: 33vh;
margin: 0;
}
}
button.selectCharacterButtonLeft {
left: 33vw;
}
button.selectCharacterButtonRight {
right: 33vw;
}
}
@media only screen and (max-width: 800px) {
form.selectCompanionScene button.selectCharacterButtonLeft{
left: 5vw;
}
form.selectCompanionScene button.selectCharacterButtonRight{
right: 5vw;
}
}
</style>

View File

@ -1,33 +1,50 @@
<script lang="typescript"> <script lang="typescript">
import { AudioContext } from 'standardized-audio-context';
import {SoundMeter} from "../Phaser/Components/SoundMeter"; import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {onDestroy} from "svelte"; import {onDestroy} from "svelte";
export let stream: MediaStream; export let stream: MediaStream|null;
let volume = 0; let volume = 0;
console.log('stream', stream); const NB_BARS = 5;
if (stream.getAudioTracks().length > 0) { let timeout;
const soundMeter = new SoundMeter(); const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext()); let display = false;
const timeout = setInterval(() => { $: {
try{ if (stream && stream.getAudioTracks().length > 0) {
volume = parseInt((soundMeter.getVolume() / 10).toFixed(0)); display = true;
console.log(volume); soundMeter.connectToSource(stream, new AudioContext());
}catch(err){
if (timeout) {
clearInterval(timeout);
} }
}, 100);
onDestroy(() => { timeout = setInterval(() => {
clearInterval(timeout); try{
}) volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
//console.log(volume);
}catch(err){
}
}, 100);
} else {
display = false;
}
} }
onDestroy(() => {
soundMeter.stop();
if (timeout) {
clearInterval(timeout);
}
})
</script> </script>
<div class="sound-progress" class:active={stream?.getAudioTracks().length > 0}> <div class="sound-progress" class:active={display}>
<span class:active={volume > 1}></span> <span class:active={volume > 1}></span>
<span class:active={volume > 2}></span> <span class:active={volume > 2}></span>
<span class:active={volume > 3}></span> <span class:active={volume > 3}></span>

View File

@ -0,0 +1,52 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import megaphoneImg from "./images/megaphone.svg";
import {soundPlayingStore} from "../../Stores/SoundPlayingStore";
import {afterUpdate, onMount} from "svelte";
export let url: string;
let audio: HTMLAudioElement;
function soundEnded() {
soundPlayingStore.soundEnded();
}
afterUpdate(() => {
audio.play();
});
</script>
<div class="audio-playing" transition:fly="{{ x: 210, duration: 500 }}">
<img src={megaphoneImg} alt="Audio playing" />
<p>Audio message</p>
<audio bind:this={audio} src={url} on:ended={soundEnded} >
<track kind="captions">
</audio>
</div>
<style lang="scss">
/*audio html when audio message playing*/
.audio-playing {
position: absolute;
width: 200px;
height: 54px;
right: 0;
top: 40px;
transition: all 0.1s ease-out;
background-color: black;
border-radius: 30px 0 0 30px;
display: inline-flex;
img {
border-radius: 50%;
background-color: #ffda01;
padding: 10px;
}
p {
color: white;
margin-left: 10px;
margin-top: 14px;
}
}
</style>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.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 451.7 512" style="enable-background:new 0 0 451.7 512;" xml:space="preserve">
<path d="M436.9,212.6L237.2,12.9c-11.7-11.7-30.7-11.7-42.4,0s-11.7,30.7,0,42.4L394.5,255c11.5,11.9,30.5,12.2,42.4,0.7
c11.9-11.5,12.2-30.5,0.7-42.4C437.4,213.1,437.2,212.8,436.9,212.6z"/>
<path d="M179.5,83.1l-1.5,7.5c-10.4,53-36,103.4-70.6,144.3l109,108.3c40.7-34.9,90.2-61.5,143.1-72.3l7.5-1.5L179.5,83.1z"/>
<path d="M87.4,257l-74.2,74.2c-17.6,17.6-17.6,46.1,0,63.6c0,0,0,0,0,0l42.4,42.4c17.6,17.6,46.1,17.6,63.6,0c0,0,0,0,0,0l74.2-74.2
L87.4,257z M98,373.7c-6.1,5.6-15.6,5.3-21.2-0.8c-5.4-5.8-5.4-14.7,0-20.5l21.2-21.2c6-5.8,15.5-5.6,21.2,0.4
c5.6,5.8,5.6,15,0,20.8L98,373.7z"/>
<path d="M256.1,445.3l20.4-20.4c17.6-17.6,17.6-46.1,0-63.6l-15.1-15.2c-8.4,5.7-16.4,11.7-24.2,18.3l18.1,18.1
c5.8,5.9,5.8,15.3,0,21.2l-20.7,20.8l-30.5-29.5l-42.4,42.4l68.1,65.9c11.7,11.7,30.7,11.7,42.4,0c11.7-11.7,11.7-30.7,0-42.4l0,0
L256.1,445.3z"/>
<path d="M316.7,0c-8.3,0-15,6.7-15,15v30c0,8.3,6.7,15,15,15c8.3,0,15-6.7,15-15V15C331.7,6.7,325,0,316.7,0z"/>
<path d="M436.7,120h-30c-8.3,0-15,6.7-15,15s6.7,15,15,15h30c8.3,0,15-6.7,15-15S445,120,436.7,120z"/>
<path d="M417.3,34.4c-5.9-5.9-15.4-5.9-21.2,0l-30,30c-6,5.8-6.1,15.3-0.4,21.2c5.8,6,15.3,6.1,21.2,0.4c0.1-0.1,0.2-0.2,0.4-0.4
l30-30C423.2,49.7,423.2,40.2,417.3,34.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,92 @@
<script lang="typescript">
import { Game } from "../../Phaser/Game/Game";
import { SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
export let game: Game;
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName);
function selectLeft() {
selectCharacterScene.moveToLeft();
}
function selectRight() {
selectCharacterScene.moveToRight();
}
function cameraScene() {
selectCharacterScene.nextSceneToCameraScene();
}
function customizeScene() {
selectCharacterScene.nextSceneToCustomizeScene();
}
</script>
<form class="selectCharacterScene">
<section class="text-center">
<h2>Select your WOKA</h2>
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> &lt; </button>
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={ selectRight }> &gt; </button>
</section>
<section class="action">
<button type="submit" class="selectCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ cameraScene }>Continue</button>
<button type="submit" class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn" on:click|preventDefault={ customizeScene }>Customize your WOKA</button>
</section>
</form>
<style lang="scss">
form.selectCharacterScene {
font-family: "Press Start 2P";
pointer-events: auto;
color: #ebeeee;
section {
margin: 10px;
&.action {
text-align: center;
margin-top: 55vh;
}
h2 {
font-family: "Press Start 2P";
margin: 1px;
}
&.text-center {
text-align: center;
}
button.selectCharacterButton {
position: absolute;
top: 33vh;
margin: 0;
}
}
button {
font-family: "Press Start 2P";
&.selectCharacterButtonLeft {
left: 33vw;
}
&.selectCharacterButtonRight {
right: 33vw;
}
}
}
@media only screen and (max-width: 800px) {
form.selectCharacterScene button.selectCharacterButtonLeft{
left: 5vw;
}
form.selectCharacterScene button.selectCharacterButtonRight{
right: 5vw;
}
}
</style>

View File

@ -4,7 +4,7 @@ import {RoomConnection} from "./RoomConnection";
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels"; import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
import {GameConnexionTypes, urlManager} from "../Url/UrlManager"; import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
import {localUserStore} from "./LocalUserStore"; import {localUserStore} from "./LocalUserStore";
import {LocalUser} from "./LocalUser"; import {CharacterTexture, LocalUser} from "./LocalUser";
import {Room} from "./Room"; import {Room} from "./Room";
@ -46,8 +46,8 @@ class ConnectionManager {
urlManager.pushRoomIdToUrl(room); urlManager.pushRoomIdToUrl(room);
return Promise.resolve(room); return Promise.resolve(room);
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) { } else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
const localUser = localUserStore.getLocalUser();
let localUser = localUserStore.getLocalUser();
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) { if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
this.localUser = localUser; this.localUser = localUser;
try { try {
@ -57,16 +57,42 @@ class ConnectionManager {
console.error('JWT token invalid. Did it expire? Login anonymously instead.'); console.error('JWT token invalid. Did it expire? Login anonymously instead.');
await this.anonymousLogin(); await this.anonymousLogin();
} }
} else { }else{
await this.anonymousLogin(); await this.anonymousLogin();
} }
let roomId: string
localUser = localUserStore.getLocalUser();
if(!localUser){
throw "Error to store local user data";
}
let roomId: string;
if (connexionType === GameConnexionTypes.empty) { if (connexionType === GameConnexionTypes.empty) {
roomId = START_ROOM_URL; roomId = START_ROOM_URL;
} else { } else {
roomId = window.location.pathname + window.location.search + window.location.hash; roomId = window.location.pathname + window.location.search + window.location.hash;
} }
return Promise.resolve(new Room(roomId));
//get detail map for anonymous login and set texture in local storage
const room = new Room(roomId);
const mapDetail = await room.getMapDetail();
if(mapDetail.textures != undefined && mapDetail.textures.length > 0) {
//check if texture was changed
if(localUser.textures.length === 0){
localUser.textures = mapDetail.textures;
}else{
mapDetail.textures.forEach((newTexture) => {
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
return;
}
localUser?.textures.push(newTexture)
});
}
this.localUser = localUser;
localUserStore.saveUser(localUser);
}
return Promise.resolve(room);
} }
return Promise.reject(new Error('Invalid URL')); return Promise.reject(new Error('Invalid URL'));

View File

@ -10,7 +10,7 @@ export interface CharacterTexture {
export const maxUserNameLength: number = MAX_USERNAME_LENGTH; export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
export function isUserNameValid(value: unknown): boolean { export function isUserNameValid(value: unknown): boolean {
return typeof value === "string" && value.length > 0 && value.length < maxUserNameLength && value.indexOf(' ') === -1; return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && value.indexOf(' ') === -1;
} }
export function areCharacterLayersValid(value: string[] | null): boolean { export function areCharacterLayersValid(value: string[] | null): boolean {
@ -24,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
} }
export class LocalUser { export class LocalUser {
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) { constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
} }
} }

View File

@ -1,10 +1,17 @@
import Axios from "axios"; import Axios from "axios";
import {PUSHER_URL} from "../Enum/EnvironmentVariable"; import {PUSHER_URL} from "../Enum/EnvironmentVariable";
import type {CharacterTexture} from "./LocalUser";
export class MapDetail{
constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) {
}
}
export class Room { export class Room {
public readonly id: string; public readonly id: string;
public readonly isPublic: boolean; public readonly isPublic: boolean;
private mapUrl: string|undefined; private mapUrl: string|undefined;
private textures: CharacterTexture[]|undefined;
private instance: string|undefined; private instance: string|undefined;
private _search: URLSearchParams; private _search: URLSearchParams;
@ -50,10 +57,10 @@ export class Room {
return {roomId, hash} return {roomId, hash}
} }
public async getMapUrl(): Promise<string> { public async getMapDetail(): Promise<MapDetail> {
return new Promise<string>((resolve, reject) => { return new Promise<MapDetail>((resolve, reject) => {
if (this.mapUrl !== undefined) { if (this.mapUrl !== undefined && this.textures != undefined) {
resolve(this.mapUrl); resolve(new MapDetail(this.mapUrl, this.textures));
return; return;
} }
@ -61,7 +68,7 @@ export class Room {
const match = /_\/[^/]+\/(.+)/.exec(this.id); const match = /_\/[^/]+\/(.+)/.exec(this.id);
if (!match) throw new Error('Could not extract url from "'+this.id+'"'); if (!match) throw new Error('Could not extract url from "'+this.id+'"');
this.mapUrl = window.location.protocol+'//'+match[1]; this.mapUrl = window.location.protocol+'//'+match[1];
resolve(this.mapUrl); resolve(new MapDetail(this.mapUrl, this.textures));
return; return;
} else { } else {
// We have a private ID, we need to query the map URL from the server. // We have a private ID, we need to query the map URL from the server.
@ -71,7 +78,7 @@ export class Room {
params: urlParts params: urlParts
}).then(({data}) => { }).then(({data}) => {
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
resolve(data.mapUrl); resolve(data);
return; return;
}).catch((reason) => { }).catch((reason) => {
reject(reason); reject(reason);

View File

@ -12,8 +12,9 @@ const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true"; const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
const POSITION_DELAY = 200; // Wait 200ms between sending position events const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '8'); export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) ); export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
@ -31,7 +32,5 @@ export {
TURN_USER, TURN_USER,
TURN_PASSWORD, TURN_PASSWORD,
JITSI_URL, JITSI_URL,
JITSI_PRIVATE_MODE, JITSI_PRIVATE_MODE
MAX_USERNAME_LENGTH,
MAX_PER_GROUP
} }

View File

@ -28,13 +28,13 @@ export class MobileJoystick extends VirtualJoystick {
this.visible = false; this.visible = false;
this.enable = false; this.enable = false;
this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => { this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
if (!pointer.wasTouch) { if (!pointer.wasTouch) {
return; return;
} }
// Let's only display the joystick if there is one finger on the screen // Let's only display the joystick if there is one finger on the screen
if (pointer.event.touches.length === 1) { if ((pointer.event as TouchEvent).touches.length === 1) {
this.x = pointer.x; this.x = pointer.x;
this.y = pointer.y; this.y = pointer.y;
this.visible = true; this.visible = true;

View File

@ -1,3 +1,5 @@
import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context';
/** /**
* Class to measure the sound volume of a media stream * Class to measure the sound volume of a media stream
*/ */
@ -5,10 +7,10 @@ export class SoundMeter {
private instant: number; private instant: number;
private clip: number; private clip: number;
//private script: ScriptProcessorNode; //private script: ScriptProcessorNode;
private analyser: AnalyserNode|undefined; private analyser: IAnalyserNode<IAudioContext>|undefined;
private dataArray: Uint8Array|undefined; private dataArray: Uint8Array|undefined;
private context: AudioContext|undefined; private context: IAudioContext|undefined;
private source: MediaStreamAudioSourceNode|undefined; private source: IMediaStreamAudioSourceNode<IAudioContext>|undefined;
constructor() { constructor() {
this.instant = 0.0; this.instant = 0.0;
@ -16,7 +18,7 @@ export class SoundMeter {
//this.script = context.createScriptProcessor(2048, 1, 1); //this.script = context.createScriptProcessor(2048, 1, 1);
} }
private init(context: AudioContext) { private init(context: IAudioContext) {
this.context = context; this.context = context;
this.analyser = this.context.createAnalyser(); this.analyser = this.context.createAnalyser();
@ -25,8 +27,12 @@ export class SoundMeter {
this.dataArray = new Uint8Array(bufferLength); this.dataArray = new Uint8Array(bufferLength);
} }
public connectToSource(stream: MediaStream, context: AudioContext): void public connectToSource(stream: MediaStream, context: IAudioContext): void
{ {
if (this.source !== undefined) {
this.stop();
}
this.init(context); this.init(context);
this.source = this.context?.createMediaStreamSource(stream); this.source = this.context?.createMediaStreamSource(stream);
@ -81,56 +87,3 @@ export class SoundMeter {
} }
// Meter class that generates a number correlated to audio volume.
// The meter class itself displays nothing, but it makes the
// instantaneous and time-decaying volumes available for inspection.
// It also reports on the fraction of samples that were at or near
// the top of the measurement range.
/*function SoundMeter(context) {
this.context = context;
this.instant = 0.0;
this.slow = 0.0;
this.clip = 0.0;
this.script = context.createScriptProcessor(2048, 1, 1);
const that = this;
this.script.onaudioprocess = function(event) {
const input = event.inputBuffer.getChannelData(0);
let i;
let sum = 0.0;
let clipcount = 0;
for (i = 0; i < input.length; ++i) {
sum += input[i] * input[i];
if (Math.abs(input[i]) > 0.99) {
clipcount += 1;
}
}
that.instant = Math.sqrt(sum / input.length);
that.slow = 0.95 * that.slow + 0.05 * that.instant;
that.clip = clipcount / input.length;
};
}
SoundMeter.prototype.connectToSource = function(stream, callback) {
console.log('SoundMeter connecting');
try {
this.mic = this.context.createMediaStreamSource(stream);
this.mic.connect(this.script);
// necessary to make sample run, but should not be.
this.script.connect(this.context.destination);
if (typeof callback !== 'undefined') {
callback(null);
}
} catch (e) {
console.error(e);
if (typeof callback !== 'undefined') {
callback(e);
}
}
};
SoundMeter.prototype.stop = function() {
this.mic.disconnect();
this.script.disconnect();
};
*/

View File

@ -1,44 +0,0 @@
import Container = Phaser.GameObjects.Container;
import type {Scene} from "phaser";
import GameObject = Phaser.GameObjects.GameObject;
import Rectangle = Phaser.GameObjects.Rectangle;
export class SoundMeterSprite extends Container {
private rectangles: Rectangle[] = new Array<Rectangle>();
private static readonly NB_BARS = 20;
constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) {
super(scene, x, y, children);
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16));
this.add(rectangle);
this.rectangles.push(rectangle);
}
}
/**
* A number between 0 and 100
*
* @param volume
*/
public setVolume(volume: number): void {
const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS;
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
if (normalizedVolume < i) {
this.rectangles[i].alpha = 0.5;
} else {
this.rectangles[i].alpha = 1;
}
}
}
public getWidth(): number {
return SoundMeterSprite.NB_BARS * 13;
}
}

View File

@ -69,7 +69,7 @@ export abstract class DirtyScene extends ResizableScene {
return this.dirty || this.objectListChanged; return this.dirty || this.objectListChanged;
} }
public onResize(ev: UIEvent): void { public onResize(): void {
this.objectListChanged = true; this.objectListChanged = true;
} }
} }

View File

@ -21,14 +21,22 @@ export class Game extends Phaser.Game {
constructor(GameConfig: Phaser.Types.Core.GameConfig) { constructor(GameConfig: Phaser.Types.Core.GameConfig) {
super(GameConfig); super(GameConfig);
window.addEventListener('resize', (event) => { this.scale.on(Phaser.Scale.Events.RESIZE, () => {
for (const scene of this.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize();
}
}
})
/*window.addEventListener('resize', (event) => {
// Let's trigger the onResize method of any active scene that is a ResizableScene // Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of this.scene.getScenes(true)) { for (const scene of this.scene.getScenes(true)) {
if (scene instanceof ResizableScene) { if (scene instanceof ResizableScene) {
scene.onResize(event); scene.onResize(event);
} }
} }
}); });*/
} }
public step(time: number, delta: number) public step(time: number, delta: number)

View File

@ -2,11 +2,13 @@ import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager"; import {connectionManager} from "../../Connexion/ConnectionManager";
import type {Room} from "../../Connexion/Room"; import type {Room} from "../../Connexion/Room";
import {MenuScene, MenuSceneName} from "../Menu/MenuScene"; import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
import {LoginSceneName} from "../Login/LoginScene"; import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene"; import {EnableCameraSceneName} from "../Login/EnableCameraScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; import {localUserStore} from "../../Connexion/LocalUserStore";
import {get} from "svelte/store";
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
export interface HasMovedEvent { export interface HasMovedEvent {
direction: string; direction: string;
@ -76,11 +78,11 @@ export class GameManager {
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> { public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
const roomID = room.id; const roomID = room.id;
const mapUrl = await room.getMapUrl(); const mapDetail = await room.getMapDetail();
const gameIndex = scenePlugin.getIndex(roomID); const gameIndex = scenePlugin.getIndex(roomID);
if(gameIndex === -1){ if(gameIndex === -1){
const game : Phaser.Scene = new GameScene(room, mapUrl); const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl);
scenePlugin.add(roomID, game, false); scenePlugin.add(roomID, game, false);
} }
} }
@ -89,7 +91,11 @@ export class GameManager {
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id)) console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
scenePlugin.start(this.currentGameSceneName || this.startRoom.id); scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
scenePlugin.launch(MenuSceneName); scenePlugin.launch(MenuSceneName);
scenePlugin.launch(HelpCameraSettingsSceneName);//700
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
helpCameraSettingsVisibleStore.set(true);
localUserStore.setHelpCameraSettingsShown();
}
} }
public gameSceneIsCreated(scene: GameScene) { public gameSceneIsCreated(scene: GameScene) {

View File

@ -253,11 +253,6 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.image(joystickBaseKey, joystickBaseImg); this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg); this.load.image(joystickThumbKey, joystickThumbImg);
} }
//todo: in an emote manager.
this.load.spritesheet('emote-music', 'resources/emotes/pipo-popupemotes005.png', {
frameHeight: 32,
frameWidth: 32,
});
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
@ -306,7 +301,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//eslint-disable-next-line @typescript-eslint/no-explicit-any //eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.load as any).rexWebFont({ (this.load as any).rexWebFont({
custom: { custom: {
families: ['Press Start 2P'], families: ['Press Start 2P'],
urls: ['/resources/fonts/fonts.css'], urls: ['/resources/fonts/fonts.css'],
@ -1052,6 +1047,8 @@ ${escapedMessage}
this.pinchManager?.destroy(); this.pinchManager?.destroy();
this.emoteManager.destroy(); this.emoteManager.destroy();
mediaManager.hideGameOverlay();
for(const iframeEvents of this.iframeSubscriptionList){ for(const iframeEvents of this.iframeSubscriptionList){
iframeEvents.unsubscribe(); iframeEvents.unsubscribe();
} }
@ -1252,7 +1249,10 @@ ${escapedMessage}
this.companion, this.companion,
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
); );
this.CurrentPlayer.on('pointerdown', () => { this.CurrentPlayer.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) {
return; //we don't want the menu to open when pinching on a touch screen.
}
this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements)) this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements))
}) })
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => { this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
@ -1558,8 +1558,8 @@ ${escapedMessage}
this.connection?.emitActionableEvent(itemId, eventName, state, parameters); this.connection?.emitActionableEvent(itemId, eventName, state, parameters);
} }
public onResize(ev: UIEvent): void { public onResize(): void {
super.onResize(ev); super.onResize();
this.reposition(); this.reposition();
// Send new viewport to server // Send new viewport to server

View File

@ -17,7 +17,9 @@ class SoundManager {
return res(sound); return res(sound);
} }
loadPlugin.audio(soundUrl, soundUrl); loadPlugin.audio(soundUrl, soundUrl);
loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl))); loadPlugin.once('filecomplete-audio-' + soundUrl, () => {
res(soundManager.add(soundUrl));
});
loadPlugin.start(); loadPlugin.start();
}); });
this.soundPromises.set(soundUrl,soundPromise); this.soundPromises.set(soundUrl,soundPromise);

View File

@ -11,6 +11,10 @@ import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser"; import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import { MenuScene } from "../Menu/MenuScene"; import { MenuScene } from "../Menu/MenuScene";
import { SelectCharacterSceneName } from "./SelectCharacterScene"; import { SelectCharacterSceneName } from "./SelectCharacterScene";
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
export const CustomizeSceneName = "CustomizeScene"; export const CustomizeSceneName = "CustomizeScene";
@ -22,10 +26,10 @@ export class CustomizeScene extends AbstractCharacterScene {
private selectedLayers: number[] = [0]; private selectedLayers: number[] = [0];
private containersRow: Container[][] = []; private containersRow: Container[][] = [];
private activeRow:number = 0; public activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = []; private layers: BodyResourceDescriptionInterface[][] = [];
private customizeSceneElement!: Phaser.GameObjects.DOMElement; protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
constructor() { constructor() {
super({ super({
@ -36,7 +40,6 @@ export class CustomizeScene extends AbstractCharacterScene {
preload() { preload() {
this.load.html(customizeSceneKey, 'resources/html/CustomCharacterScene.html'); this.load.html(customizeSceneKey, 'resources/html/CustomCharacterScene.html');
this.layers = loadAllLayers(this.load);
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => { this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => { bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){ if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
@ -44,43 +47,28 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
}); });
this.lazyloadingAttempt = true;
}); });
this.layers = loadAllLayers(this.load);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); addLoader(this);
} }
create() { create() {
this.customizeSceneElement = this.add.dom(-1000, 0).createFromCache(customizeSceneKey); customCharacterSceneVisibleStore.set(true);
this.centerXDomElement(this.customizeSceneElement, 150); this.events.addListener('wake', () => {
MenuScene.revealMenusAfterInit(this.customizeSceneElement, customizeSceneKey); waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
this.customizeSceneElement.addListener('click'); customCharacterSceneVisibleStore.set(true);
this.customizeSceneElement.on('click', (event:MouseEvent) => {
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'customizeSceneButtonLeft') {
this.moveCursorHorizontally(-1);
}else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonRight') {
this.moveCursorHorizontally(1);
}else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonDown') {
this.moveCursorVertically(1);
}else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonUp') {
this.moveCursorVertically(-1);
}else if((event?.target as HTMLInputElement).id === 'customizeSceneFormBack') {
if(this.activeRow > 0){
this.moveCursorVertically(-1);
}else{
this.backToPreviousScene();
}
}else if((event?.target as HTMLButtonElement).id === 'customizeSceneFormSubmit') {
if(this.activeRow < 5){
this.moveCursorVertically(1);
}else{
this.nextSceneToCamera();
}
}
}); });
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33) this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33)
this.Rectangle.setStrokeStyle(2, 0xFFFFFF); this.Rectangle.setStrokeStyle(2, 0xFFFFFF);
this.add.existing(this.Rectangle); this.add.existing(this.Rectangle);
@ -116,7 +104,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.onResize(); this.onResize();
} }
private moveCursorHorizontally(index: number): void { public moveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index; this.selectedLayers[this.activeRow] += index;
if (this.selectedLayers[this.activeRow] < 0) { if (this.selectedLayers[this.activeRow] < 0) {
this.selectedLayers[this.activeRow] = 0 this.selectedLayers[this.activeRow] = 0
@ -128,27 +116,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.saveInLocalStorage(); this.saveInLocalStorage();
} }
private moveCursorVertically(index:number): void { public moveCursorVertically(index:number): void {
if(index === -1 && this.activeRow === 5){
const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
button.innerHTML = `Next <img src="resources/objects/arrow_up.png"/>`;
}
if(index === 1 && this.activeRow === 4){
const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
button.innerText = 'Finish';
}
if(index === -1 && this.activeRow === 1){
const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement;
button.innerText = `Return`;
}
if(index === 1 && this.activeRow === 0){
const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement;
button.innerHTML = `Back <img src="resources/objects/arrow_up.png"/>`;
}
this.activeRow += index; this.activeRow += index;
if (this.activeRow < 0) { if (this.activeRow < 0) {
@ -262,6 +230,10 @@ export class CustomizeScene extends AbstractCharacterScene {
update(time: number, delta: number): void { update(time: number, delta: number): void {
if(this.lazyloadingAttempt){
this.moveLayers();
this.lazyloadingAttempt = false;
}
} }
public onResize(): void { public onResize(): void {
@ -269,8 +241,6 @@ export class CustomizeScene extends AbstractCharacterScene {
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2; this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3; this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
this.centerXDomElement(this.customizeSceneElement, 150);
} }
private nextSceneToCamera(){ private nextSceneToCamera(){
@ -288,12 +258,16 @@ export class CustomizeScene extends AbstractCharacterScene {
gameManager.setCharacterLayers(layers); gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName); this.scene.sleep(CustomizeSceneName);
this.scene.remove(SelectCharacterSceneName); waScaleManager.restoreZoom();
this.events.removeListener('wake');
gameManager.tryResumingGame(this, EnableCameraSceneName); gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
} }
private backToPreviousScene(){ private backToPreviousScene(){
this.scene.sleep(CustomizeSceneName); this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.scene.run(SelectCharacterSceneName); this.scene.run(SelectCharacterSceneName);
customCharacterSceneVisibleStore.set(false);
} }
} }

View File

@ -3,7 +3,6 @@ import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image; import Image = Phaser.GameObjects.Image;
import {mediaManager} from "../../WebRtc/MediaManager"; import {mediaManager} from "../../WebRtc/MediaManager";
import {SoundMeter} from "../Components/SoundMeter"; import {SoundMeter} from "../Components/SoundMeter";
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import {PinchManager} from "../UserInput/PinchManager";
@ -11,304 +10,41 @@ import Zone = Phaser.GameObjects.Zone;
import { MenuScene } from "../Menu/MenuScene"; import { MenuScene } from "../Menu/MenuScene";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import { import {
audioConstraintStore,
enableCameraSceneVisibilityStore, enableCameraSceneVisibilityStore,
localStreamStore,
mediaStreamConstraintsStore,
videoConstraintStore
} from "../../Stores/MediaStore"; } from "../../Stores/MediaStore";
import type {Unsubscriber} from "svelte/store";
export const EnableCameraSceneName = "EnableCameraScene"; export const EnableCameraSceneName = "EnableCameraScene";
enum LoginTextures {
playButton = "play_button",
icon = "icon",
mainFont = "main_font",
arrowRight = "arrow_right",
arrowUp = "arrow_up"
}
const enableCameraSceneKey = 'enableCameraScene';
export class EnableCameraScene extends ResizableScene { export class EnableCameraScene extends ResizableScene {
private textField!: TextField;
private cameraNameField!: TextField;
private arrowLeft!: Image;
private arrowRight!: Image;
private arrowDown!: Image;
private arrowUp!: Image;
private microphonesList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
private camerasList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
private cameraSelected: number = 0;
private microphoneSelected: number = 0;
private soundMeter: SoundMeter;
private soundMeterSprite!: SoundMeterSprite;
private microphoneNameField!: TextField;
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
private mobileTapZone!: Zone;
private localStreamStoreUnsubscriber!: Unsubscriber;
constructor() { constructor() {
super({ super({
key: EnableCameraSceneName key: EnableCameraSceneName
}); });
this.soundMeter = new SoundMeter();
} }
preload() { preload() {
this.load.html(enableCameraSceneKey, 'resources/html/EnableCameraScene.html');
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png");
this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
} }
create() { create() {
this.enableCameraSceneElement = this.add.dom(-1000, 0).createFromCache(enableCameraSceneKey);
this.centerXDomElement(this.enableCameraSceneElement, 300);
MenuScene.revealMenusAfterInit(this.enableCameraSceneElement, enableCameraSceneKey);
const continuingButton = this.enableCameraSceneElement.getChildByID('enableCameraSceneFormSubmit') as HTMLButtonElement;
continuingButton.addEventListener('click', (e) => {
e.preventDefault();
this.login();
});
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
//this.scale.setZoom(ZOOM_LEVEL);
//Phaser.Display.Align.In.BottomCenter(this.pressReturnField, zone);
/* FIX ME */
this.textField = new TextField(this, this.scale.width / 2, 20, '');
// For mobile purposes - we need a big enough touchable area.
this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50)
.setInteractive().on("pointerdown", () => {
this.login();
});
this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, '');
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
this.arrowRight.setVisible(false);
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
this.add.existing(this.arrowRight);
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
this.arrowLeft.setVisible(false);
this.arrowLeft.flipX = true;
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
this.add.existing(this.arrowLeft);
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
this.arrowUp.setVisible(false);
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
this.add.existing(this.arrowUp);
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
this.arrowDown.setVisible(false);
this.arrowDown.flipY = true;
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
this.add.existing(this.arrowDown);
this.input.keyboard.on('keyup-ENTER', () => { this.input.keyboard.on('keyup-ENTER', () => {
this.login(); this.login();
}); });
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => {
if (result.type === 'error') {
// TODO: proper handling of the error
throw result.error;
}
this.getDevices();
if (result.stream !== null) {
this.setupStream(result.stream);
}
});
/*const mediaPromise = mediaManager.getCamera();
mediaPromise.then(this.getDevices.bind(this));
mediaPromise.then(this.setupStream.bind(this));*/
this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
this.input.keyboard.on('keydown-DOWN', this.nextMic.bind(this));
this.input.keyboard.on('keydown-UP', this.previousMic.bind(this));
this.soundMeterSprite = new SoundMeterSprite(this, 50, 50);
this.soundMeterSprite.setVisible(false);
this.add.existing(this.soundMeterSprite);
this.onResize();
enableCameraSceneVisibilityStore.showEnableCameraScene(); enableCameraSceneVisibilityStore.showEnableCameraScene();
} }
private previousCam(): void {
if (this.cameraSelected === 0 || this.camerasList.length === 0) {
return;
}
this.cameraSelected--;
videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
//mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
}
private nextCam(): void {
if (this.cameraSelected === this.camerasList.length - 1 || this.camerasList.length === 0) {
return;
}
this.cameraSelected++;
videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
// TODO: the change of camera should be OBSERVED (reactive)
//mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
}
private previousMic(): void {
if (this.microphoneSelected === 0 || this.microphonesList.length === 0) {
return;
}
this.microphoneSelected--;
audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
//mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
}
private nextMic(): void {
if (this.microphoneSelected === this.microphonesList.length - 1 || this.microphonesList.length === 0) {
return;
}
this.microphoneSelected++;
audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
// TODO: the change of camera should be OBSERVED (reactive)
//mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
}
/**
* Function called each time a camera is changed
*/
private setupStream(stream: MediaStream): void {
const img = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
img.style.display = 'none';
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
div.srcObject = stream;
this.soundMeter.connectToSource(stream, new window.AudioContext());
this.soundMeterSprite.setVisible(true);
this.updateWebCamName();
}
private updateWebCamName(): void {
if (this.camerasList.length > 1) {
let label = this.camerasList[this.cameraSelected].label;
// remove text in parenthesis
label = label.replace(/\([^()]*\)/g, '').trim();
// remove accents
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
this.cameraNameField.text = label;
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
this.arrowLeft.setVisible(this.cameraSelected > 0);
}
if (this.microphonesList.length > 1) {
let label = this.microphonesList[this.microphoneSelected].label;
// remove text in parenthesis
label = label.replace(/\([^()]*\)/g, '').trim();
// remove accents
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
this.microphoneNameField.text = label;
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
this.arrowUp.setVisible(this.microphoneSelected > 0);
}
}
public onResize(): void { public onResize(): void {
let div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let bounds = div.getBoundingClientRect();
if (!div.srcObject) {
div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
bounds = div.getBoundingClientRect();
}
this.textField.x = this.game.renderer.width / 2;
this.mobileTapZone.x = this.game.renderer.width / 2;
this.cameraNameField.x = this.game.renderer.width / 2;
this.microphoneNameField.x = this.game.renderer.width / 2;
this.cameraNameField.y = bounds.top / this.scale.zoom - 8;
this.soundMeterSprite.x = this.game.renderer.width / 2 - this.soundMeterSprite.getWidth() / 2;
this.soundMeterSprite.y = bounds.bottom / this.scale.zoom + 16;
this.microphoneNameField.y = this.soundMeterSprite.y + 22;
this.arrowRight.x = bounds.right / this.scale.zoom + 16;
this.arrowRight.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
this.arrowLeft.x = bounds.left / this.scale.zoom - 16;
this.arrowLeft.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
this.arrowDown.x = this.microphoneNameField.x + this.microphoneNameField.width / 2 + 16;
this.arrowDown.y = this.microphoneNameField.y;
this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16;
this.arrowUp.y = this.microphoneNameField.y;
const actionBtn = document.querySelector<HTMLDivElement>('#enableCameraScene .action');
if (actionBtn !== null) {
actionBtn.style.top = (this.scale.height - 65) + 'px';
}
} }
update(time: number, delta: number): void { update(time: number, delta: number): void {
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
this.centerXDomElement(this.enableCameraSceneElement, 300);
} }
private login(): void { public login(): void {
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.soundMeter.stop();
enableCameraSceneVisibilityStore.hideEnableCameraScene(); enableCameraSceneVisibilityStore.hideEnableCameraScene();
this.localStreamStoreUnsubscriber();
//mediaManager.stopCamera();
//mediaManager.stopMicrophone();
this.scene.sleep(EnableCameraSceneName); this.scene.sleep(EnableCameraSceneName);
gameManager.goToStartingMap(this.scene); gameManager.goToStartingMap(this.scene);
} }
private async getDevices() {
// TODO: switch this in a store.
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
this.microphonesList = [];
this.camerasList = [];
for (const mediaDeviceInfo of mediaDeviceInfos) {
if (mediaDeviceInfo.kind === 'audioinput') {
this.microphonesList.push(mediaDeviceInfo);
} else if (mediaDeviceInfo.kind === 'videoinput') {
this.camerasList.push(mediaDeviceInfo);
}
}
this.updateWebCamName();
}
} }

View File

@ -1,18 +1,12 @@
import {gameManager} from "../Game/GameManager"; import {gameManager} from "../Game/GameManager";
import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import { localUserStore } from "../../Connexion/LocalUserStore"; import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
import {MenuScene} from "../Menu/MenuScene";
import { isUserNameValid } from "../../Connexion/LocalUser";
import { MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
export const LoginSceneName = "LoginScene"; export const LoginSceneName = "LoginScene";
const loginSceneKey = 'loginScene';
export class LoginScene extends ResizableScene { export class LoginScene extends ResizableScene {
private loginSceneElement!: Phaser.GameObjects.DOMElement;
private name: string = ''; private name: string = '';
constructor() { constructor() {
@ -23,65 +17,25 @@ export class LoginScene extends ResizableScene {
} }
preload() { preload() {
this.load.html(loginSceneKey, 'resources/html/loginScene.html');
} }
create() { create() {
this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey); loginSceneVisibleStore.set(true);
this.centerXDomElement(this.loginSceneElement, 200);
MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey);
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement;
inputElement.value = localUserStore.getName() ?? '';
inputElement.focus();
inputElement.addEventListener('keypress', (event: KeyboardEvent) => {
if(inputElement.value.length >= MAX_USERNAME_LENGTH){
event.preventDefault();
return;
}
pErrorElement.innerHTML = '';
if(inputElement.value && !isUserNameValid(inputElement.value)){
pErrorElement.innerHTML = 'Invalid user name: No spaces are allowed.';
}
if (event.key === 'Enter') {
event.preventDefault();
this.login(inputElement);
return;
}
});
const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement;
continuingButton.addEventListener('click', (e) => {
e.preventDefault();
this.login(inputElement);
});
} }
private login(inputElement: HTMLInputElement): void { public login(name: string): void {
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement; name = name.trim();
this.name = inputElement.value; gameManager.setPlayerName(name);
if (this.name === '') {
pErrorElement.innerHTML = 'The name is empty';
return
}
if(!isUserNameValid(this.name)){
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
return
}
if (this.name === '') return
gameManager.setPlayerName(this.name);
this.scene.stop(LoginSceneName) this.scene.stop(LoginSceneName)
gameManager.tryResumingGame(this, SelectCharacterSceneName); gameManager.tryResumingGame(this, SelectCharacterSceneName);
this.scene.remove(LoginSceneName) this.scene.remove(LoginSceneName);
loginSceneVisibleStore.set(false);
} }
update(time: number, delta: number): void { update(time: number, delta: number): void {
} }
public onResize(ev: UIEvent): void { public onResize(): void {
this.centerXDomElement(this.loginSceneElement, 200);
} }
} }

View File

@ -2,7 +2,7 @@ import {Scene} from "phaser";
import DOMElement = Phaser.GameObjects.DOMElement; import DOMElement = Phaser.GameObjects.DOMElement;
export abstract class ResizableScene extends Scene { export abstract class ResizableScene extends Scene {
public abstract onResize(ev: UIEvent): void; public abstract onResize(): void;
/** /**
* Centers the DOM element on the X axis. * Centers the DOM element on the X axis.
@ -17,7 +17,7 @@ export abstract class ResizableScene extends Scene {
&& object.node && object.node
&& object.node.getBoundingClientRect().width > 0 && object.node.getBoundingClientRect().width > 0
? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom) ? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom)
: (300 / this.scale.zoom) : (defaultWidth / this.scale.zoom)
); );
} }
} }

View File

@ -4,49 +4,50 @@ export class SelectCharacterMobileScene extends SelectCharacterScene {
create(){ create(){
super.create(); super.create();
this.onResize();
this.selectedRectangle.destroy(); this.selectedRectangle.destroy();
} }
protected defineSetupPlayer(numero: number){ protected defineSetupPlayer(num: number){
const deltaX = 30; const deltaX = 30;
const deltaY = 2; const deltaY = 2;
let [playerX, playerY] = this.getCharacterPosition(); let [playerX, playerY] = this.getCharacterPosition();
let playerVisible = true; let playerVisible = true;
let playerScale = 1.5; let playerScale = 1.5;
let playserOpactity = 1; let playerOpacity = 1;
if( this.currentSelectUser !== numero ){ if( this.currentSelectUser !== num ){
playerVisible = false; playerVisible = false;
} }
if( numero === (this.currentSelectUser + 1) ){ if( num === (this.currentSelectUser + 1) ){
playerY -= deltaY; playerY -= deltaY;
playerX += deltaX; playerX += deltaX;
playerScale = 0.8; playerScale = 0.8;
playserOpactity = 0.6; playerOpacity = 0.6;
playerVisible = true; playerVisible = true;
} }
if( numero === (this.currentSelectUser + 2) ){ if( num === (this.currentSelectUser + 2) ){
playerY -= deltaY; playerY -= deltaY;
playerX += (deltaX * 2); playerX += (deltaX * 2);
playerScale = 0.8; playerScale = 0.8;
playserOpactity = 0.6; playerOpacity = 0.6;
playerVisible = true; playerVisible = true;
} }
if( numero === (this.currentSelectUser - 1) ){ if( num === (this.currentSelectUser - 1) ){
playerY -= deltaY; playerY -= deltaY;
playerX -= deltaX; playerX -= deltaX;
playerScale = 0.8; playerScale = 0.8;
playserOpactity = 0.6; playerOpacity = 0.6;
playerVisible = true; playerVisible = true;
} }
if( numero === (this.currentSelectUser - 2) ){ if( num === (this.currentSelectUser - 2) ){
playerY -= deltaY; playerY -= deltaY;
playerX -= (deltaX * 2); playerX -= (deltaX * 2);
playerScale = 0.8; playerScale = 0.8;
playserOpactity = 0.6; playerOpacity = 0.6;
playerVisible = true; playerVisible = true;
} }
return {playerX, playerY, playerScale, playserOpactity, playerVisible} return {playerX, playerY, playerScale, playerOpacity, playerVisible}
} }
/** /**

View File

@ -11,6 +11,10 @@ import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import {PinchManager} from "../UserInput/PinchManager";
import {MenuScene} from "../Menu/MenuScene"; import {MenuScene} from "../Menu/MenuScene";
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene"; export const SelectCharacterSceneName = "SelectCharacterScene";
@ -27,6 +31,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement; protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement;
protected currentSelectUser = 0; protected currentSelectUser = 0;
protected pointerClicked: boolean = false;
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
constructor() { constructor() {
super({ super({
@ -41,44 +48,36 @@ export class SelectCharacterScene extends AbstractCharacterScene {
bodyResourceDescriptions.forEach((bodyResourceDescription) => { bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription); this.playerModels.push(bodyResourceDescription);
}); });
}) this.lazyloadingAttempt = true;
});
this.playerModels = loadAllDefaultModels(this.load); this.playerModels = loadAllDefaultModels(this.load);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); addLoader(this);
} }
create() { create() {
selectCharacterSceneVisibleStore.set(true);
this.selectCharacterSceneElement = this.add.dom(-1000, 0).createFromCache(selectCharacterKey); this.events.addListener('wake', () => {
this.centerXDomElement(this.selectCharacterSceneElement, 150); waScaleManager.saveZoom();
MenuScene.revealMenusAfterInit(this.selectCharacterSceneElement, selectCharacterKey); waScaleManager.zoomModifier = isMobile() ? 2 : 1;
selectCharacterSceneVisibleStore.set(true);
this.selectCharacterSceneElement.addListener('click');
this.selectCharacterSceneElement.on('click', (event:MouseEvent) => {
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') {
this.moveToLeft();
}else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') {
this.moveToRight();
}else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormSubmit') {
this.nextSceneToCameraScene();
}else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormCustomYourOwnSubmit') {
this.nextSceneToCustomizeScene();
}
}); });
if (touchScreenManager.supportTouchScreen) { if (touchScreenManager.supportTouchScreen) {
new PinchManager(this); new PinchManager(this);
} }
waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16; const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF); this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
this.selectedRectangle.setDepth(2); this.selectedRectangle.setDepth(2);
/*create user*/ /*create user*/
this.createCurrentPlayer(); this.createCurrentPlayer();
const playerNumber = localUserStore.getPlayerCharacterIndex();
this.input.keyboard.on('keyup-ENTER', () => { this.input.keyboard.on('keyup-ENTER', () => {
return this.nextSceneToCameraScene(); return this.nextSceneToCameraScene();
@ -106,9 +105,12 @@ export class SelectCharacterScene extends AbstractCharacterScene {
return; return;
} }
this.scene.stop(SelectCharacterSceneName); this.scene.stop(SelectCharacterSceneName);
waScaleManager.restoreZoom();
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]); gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
gameManager.tryResumingGame(this, EnableCameraSceneName); gameManager.tryResumingGame(this, EnableCameraSceneName);
this.scene.remove(SelectCharacterSceneName); this.players = [];
selectCharacterSceneVisibleStore.set(false);
this.events.removeListener('wake');
} }
protected nextSceneToCustomizeScene(): void { protected nextSceneToCustomizeScene(): void {
@ -116,7 +118,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
return; return;
} }
this.scene.sleep(SelectCharacterSceneName); this.scene.sleep(SelectCharacterSceneName);
waScaleManager.restoreZoom();
this.scene.run(CustomizeSceneName); this.scene.run(CustomizeSceneName);
selectCharacterSceneVisibleStore.set(false);
} }
createCurrentPlayer(): void { createCurrentPlayer(): void {
@ -133,15 +137,16 @@ export class SelectCharacterScene extends AbstractCharacterScene {
repeat: -1 repeat: -1
}); });
player.setInteractive().on("pointerdown", () => { player.setInteractive().on("pointerdown", () => {
if(this.currentSelectUser === i){ if (this.pointerClicked || this.currentSelectUser === i) {
return; return;
} }
this.pointerClicked = true;
this.currentSelectUser = i; this.currentSelectUser = i;
this.moveUser(); this.moveUser();
setTimeout(() => {this.pointerClicked = false;}, 100);
}); });
this.players.push(player); this.players.push(player);
} }
this.selectedPlayer = this.players[this.currentSelectUser]; this.selectedPlayer = this.players[this.currentSelectUser];
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name); this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
} }
@ -186,35 +191,35 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.moveUser(); this.moveUser();
} }
protected defineSetupPlayer(numero: number){ protected defineSetupPlayer(num: number){
const deltaX = 32; const deltaX = 32;
const deltaY = 32; const deltaY = 32;
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (numero % this.nbCharactersPerRow)) ); // calcul position on line users playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (num % this.nbCharactersPerRow)) ); // calcul position on line users
playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(numero / this.nbCharactersPerRow) )) ); // calcul position on column users playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(num / this.nbCharactersPerRow) )) ); // calcul position on column users
const playerVisible = true; const playerVisible = true;
const playerScale = 1; const playerScale = 1;
const playserOpactity = 1; const playerOpacity = 1;
// if selected // if selected
if( numero === this.currentSelectUser ){ if( num === this.currentSelectUser ){
this.selectedRectangle.setX(playerX); this.selectedRectangle.setX(playerX);
this.selectedRectangle.setY(playerY); this.selectedRectangle.setY(playerY);
} }
return {playerX, playerY, playerScale, playserOpactity, playerVisible} return {playerX, playerY, playerScale, playerOpacity, playerVisible}
} }
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, numero: number){ protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number){
const {playerX, playerY, playerScale, playserOpactity, playerVisible} = this.defineSetupPlayer(numero); const {playerX, playerY, playerScale, playerOpacity, playerVisible} = this.defineSetupPlayer(num);
player.setBounce(0.2); player.setBounce(0.2);
player.setCollideWorldBounds(true); player.setCollideWorldBounds(false);
player.setVisible( playerVisible ); player.setVisible( playerVisible );
player.setScale(playerScale, playerScale); player.setScale(playerScale, playerScale);
player.setAlpha(playserOpactity); player.setAlpha(playerOpacity);
player.setX(playerX); player.setX(playerX);
player.setY(playerY); player.setY(playerY);
} }
@ -238,12 +243,14 @@ export class SelectCharacterScene extends AbstractCharacterScene {
} }
update(time: number, delta: number): void { update(time: number, delta: number): void {
if(this.lazyloadingAttempt){
this.createCurrentPlayer();
this.lazyloadingAttempt = false;
}
} }
public onResize(ev: UIEvent): void { public onResize(): void {
//move position of user //move position of user
this.moveUser(); this.moveUser();
this.centerXDomElement(this.selectCharacterSceneElement, 150);
} }
} }

View File

@ -10,17 +10,18 @@ import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingM
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import {PinchManager} from "../UserInput/PinchManager";
import { MenuScene } from "../Menu/MenuScene"; import { MenuScene } from "../Menu/MenuScene";
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
import {waScaleManager} from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable";
export const SelectCompanionSceneName = "SelectCompanionScene"; export const SelectCompanionSceneName = "SelectCompanionScene";
const selectCompanionSceneKey = 'selectCompanionScene';
export class SelectCompanionScene extends ResizableScene { export class SelectCompanionScene extends ResizableScene {
private selectedCompanion!: Phaser.Physics.Arcade.Sprite; private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>(); private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private companionModels: Array<CompanionResourceDescriptionInterface> = []; private companionModels: Array<CompanionResourceDescriptionInterface> = [];
private saveZoom: number = 0;
private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement;
private currentCompanion = 0; private currentCompanion = 0;
constructor() { constructor() {
@ -30,8 +31,6 @@ export class SelectCompanionScene extends ResizableScene {
} }
preload() { preload() {
this.load.html(selectCompanionSceneKey, 'resources/html/SelectCompanionScene.html');
getAllCompanionResources(this.load).forEach(model => { getAllCompanionResources(this.load).forEach(model => {
this.companionModels.push(model); this.companionModels.push(model);
}); });
@ -42,30 +41,17 @@ export class SelectCompanionScene extends ResizableScene {
create() { create() {
this.selectCompanionSceneElement = this.add.dom(-1000, 0).createFromCache(selectCompanionSceneKey); selectCompanionSceneVisibleStore.set(true);
this.centerXDomElement(this.selectCompanionSceneElement, 150);
MenuScene.revealMenusAfterInit(this.selectCompanionSceneElement, selectCompanionSceneKey);
this.selectCompanionSceneElement.addListener('click'); waScaleManager.saveZoom();
this.selectCompanionSceneElement.on('click', (event:MouseEvent) => { waScaleManager.zoomModifier = isMobile() ? 2 : 1;
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') {
this.moveToLeft();
}else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') {
this.moveToRight();
}else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormSubmit') {
this.nextScene();
}else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormBack') {
this._nextScene();
}
});
if (touchScreenManager.supportTouchScreen) { if (touchScreenManager.supportTouchScreen) {
new PinchManager(this); new PinchManager(this);
} }
// input events // input events
this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this)); this.input.keyboard.on('keyup-ENTER', this.selectCompanion.bind(this));
this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this)); this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this));
this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this)); this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this));
@ -89,18 +75,20 @@ export class SelectCompanionScene extends ResizableScene {
} }
private nextScene(): void { public selectCompanion(): void {
localUserStore.setCompanion(this.companionModels[this.currentCompanion].name); localUserStore.setCompanion(this.companionModels[this.currentCompanion].name);
gameManager.setCompanion(this.companionModels[this.currentCompanion].name); gameManager.setCompanion(this.companionModels[this.currentCompanion].name);
this._nextScene(); this.closeScene();
} }
private _nextScene(){ public closeScene(){
// next scene // next scene
this.scene.stop(SelectCompanionSceneName); this.scene.stop(SelectCompanionSceneName);
waScaleManager.restoreZoom();
gameManager.tryResumingGame(this, EnableCameraSceneName); gameManager.tryResumingGame(this, EnableCameraSceneName);
this.scene.remove(SelectCompanionSceneName); this.scene.remove(SelectCompanionSceneName);
selectCompanionSceneVisibleStore.set(false);
} }
private createCurrentCompanion(): void { private createCurrentCompanion(): void {
@ -126,10 +114,8 @@ export class SelectCompanionScene extends ResizableScene {
this.selectedCompanion = this.companions[this.currentCompanion]; this.selectedCompanion = this.companions[this.currentCompanion];
} }
public onResize(ev: UIEvent): void { public onResize(): void {
this.moveCompanion(); this.moveCompanion();
this.centerXDomElement(this.selectCompanionSceneElement, 150);
} }
private updateSelectedCompanion(): void { private updateSelectedCompanion(): void {
@ -147,15 +133,7 @@ export class SelectCompanionScene extends ResizableScene {
this.updateSelectedCompanion(); this.updateSelectedCompanion();
} }
private moveToLeft(){ public moveToRight(){
if(this.currentCompanion === 0){
return;
}
this.currentCompanion -= 1;
this.moveCompanion();
}
private moveToRight(){
if(this.currentCompanion === (this.companions.length - 1)){ if(this.currentCompanion === (this.companions.length - 1)){
return; return;
} }
@ -163,38 +141,46 @@ export class SelectCompanionScene extends ResizableScene {
this.moveCompanion(); this.moveCompanion();
} }
private defineSetupCompanion(numero: number){ public moveToLeft(){
if(this.currentCompanion === 0){
return;
}
this.currentCompanion -= 1;
this.moveCompanion();
}
private defineSetupCompanion(num: number){
const deltaX = 30; const deltaX = 30;
const deltaY = 2; const deltaY = 2;
let [companionX, companionY] = this.getCompanionPosition(); let [companionX, companionY] = this.getCompanionPosition();
let companionVisible = true; let companionVisible = true;
let companionScale = 1.5; let companionScale = 1.5;
let companionOpactity = 1; let companionOpactity = 1;
if( this.currentCompanion !== numero ){ if( this.currentCompanion !== num ){
companionVisible = false; companionVisible = false;
} }
if( numero === (this.currentCompanion + 1) ){ if( num === (this.currentCompanion + 1) ){
companionY -= deltaY; companionY -= deltaY;
companionX += deltaX; companionX += deltaX;
companionScale = 0.8; companionScale = 0.8;
companionOpactity = 0.6; companionOpactity = 0.6;
companionVisible = true; companionVisible = true;
} }
if( numero === (this.currentCompanion + 2) ){ if( num === (this.currentCompanion + 2) ){
companionY -= deltaY; companionY -= deltaY;
companionX += (deltaX * 2); companionX += (deltaX * 2);
companionScale = 0.8; companionScale = 0.8;
companionOpactity = 0.6; companionOpactity = 0.6;
companionVisible = true; companionVisible = true;
} }
if( numero === (this.currentCompanion - 1) ){ if( num === (this.currentCompanion - 1) ){
companionY -= deltaY; companionY -= deltaY;
companionX -= deltaX; companionX -= deltaX;
companionScale = 0.8; companionScale = 0.8;
companionOpactity = 0.6; companionOpactity = 0.6;
companionVisible = true; companionVisible = true;
} }
if( numero === (this.currentCompanion - 2) ){ if( num === (this.currentCompanion - 2) ){
companionY -= deltaY; companionY -= deltaY;
companionX -= (deltaX * 2); companionX -= (deltaX * 2);
companionScale = 0.8; companionScale = 0.8;

View File

@ -1,152 +0,0 @@
import {mediaManager} from "../../WebRtc/MediaManager";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {DirtyScene} from "../Game/DirtyScene";
import {get} from "svelte/store";
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
const helpCameraSettings = 'helpCameraSettings';
/**
* The scene that show how to permit Camera and Microphone access if there are not already allowed
*/
export class HelpCameraSettingsScene extends DirtyScene {
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
private helpCameraSettingsOpened: boolean = false;
constructor() {
super({key: HelpCameraSettingsSceneName});
}
preload() {
this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
}
create(){
this.createHelpCameraSettings();
}
private createHelpCameraSettings() : void {
const middleX = this.getMiddleX();
this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
this.helpCameraSettingsElement.addListener('click');
this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
if((event?.target as HTMLInputElement).id === 'mailto') {
return;
}
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
window.location.reload();
}else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
this.closeHelpCameraSettingsOpened();
}
});
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
this.openHelpCameraSettingsOpened();
localUserStore.setHelpCameraSettingsShown();
}
mediaManager.setHelpCameraSettingsCallBack(() => {
this.openHelpCameraSettingsOpened();
});
}
private openHelpCameraSettingsOpened(): void{
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.helpCameraSettingsOpened = true;
try{
if(window.navigator.userAgent.includes('Firefox')){
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
}else if(window.navigator.userAgent.includes('Chrome')){
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
}
}catch(err) {
console.error('openHelpCameraSettingsOpened => getElementByIdOrFail => error', err);
}
const middleY = this.getMiddleY();
const middleX = this.getMiddleX();
this.tweens.add({
targets: this.helpCameraSettingsElement,
y: middleY,
x: middleX,
duration: 1000,
ease: 'Power3',
overflow: 'scroll'
});
this.dirty = true;
}
private closeHelpCameraSettingsOpened(): void{
const middleX = this.getMiddleX();
/*const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
helpCameraSettingsInfo.innerText = '';
helpCameraSettingsInfo.style.display = 'none';*/
this.helpCameraSettingsOpened = false;
this.tweens.add({
targets: this.helpCameraSettingsElement,
y: -1000,
x: middleX,
duration: 1000,
ease: 'Power3',
overflow: 'scroll'
});
this.dirty = true;
}
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
setTimeout(() => {
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
}, 250);
}
update(time: number, delta: number): void {
this.dirty = false;
}
public onResize(ev: UIEvent): void {
super.onResize(ev);
if (this.helpCameraSettingsOpened) {
const middleX = this.getMiddleX();
const middleY = this.getMiddleY();
this.tweens.add({
targets: this.helpCameraSettingsElement,
x: middleX,
y: middleY,
duration: 1000,
ease: 'Power3'
});
this.dirty = true;
}
}
private getMiddleX() : number{
return (this.scale.width / 2) -
(
this.helpCameraSettingsElement
&& this.helpCameraSettingsElement.node
&& this.helpCameraSettingsElement.node.getBoundingClientRect().width > 0
? (this.helpCameraSettingsElement.node.getBoundingClientRect().width / (2 * this.scale.zoom))
: (400 / 2)
);
}
private getMiddleY() : number{
const middleY = ((this.scale.height) - (
(this.helpCameraSettingsElement
&& this.helpCameraSettingsElement.node
&& this.helpCameraSettingsElement.node.getBoundingClientRect().height > 0
? this.helpCameraSettingsElement.node.getBoundingClientRect().height : 400 /*FIXME to use a const will be injected in HTMLElement*/)/this.scale.zoom)) / 2;
return (middleY > 0 ? middleY : 0);
}
public isDirty(): boolean {
return this.dirty;
}
}

View File

@ -2,6 +2,7 @@ import {HdpiManager} from "./HdpiManager";
import ScaleManager = Phaser.Scale.ScaleManager; import ScaleManager = Phaser.Scale.ScaleManager;
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import type {Game} from "../Game/Game"; import type {Game} from "../Game/Game";
import {ResizableScene} from "../Login/ResizableScene";
class WaScaleManager { class WaScaleManager {
@ -9,6 +10,7 @@ class WaScaleManager {
private scaleManager!: ScaleManager; private scaleManager!: ScaleManager;
private game!: Game; private game!: Game;
private actualZoom: number = 1; private actualZoom: number = 1;
private _saveZoom: number = 1;
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) { public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber); this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
@ -30,13 +32,19 @@ class WaScaleManager {
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio}); const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio; this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio); this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio)
this.scaleManager.resize(gameSize.width, gameSize.height); this.scaleManager.resize(gameSize.width, gameSize.height);
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves // Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
const style = this.scaleManager.canvas.style; const style = this.scaleManager.canvas.style;
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px'; style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px'; style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
// Note: onResize will be called twice (once here and once is Game.ts), but we have no better way.
for (const scene of this.game.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize();
}
}
this.game.markDirty(); this.game.markDirty();
} }
@ -50,6 +58,16 @@ class WaScaleManager {
this.applyNewSize(); this.applyNewSize();
} }
public saveZoom(): void {
this._saveZoom = this.hdpiManager.zoomModifier;
console.log(this._saveZoom);
}
public restoreZoom(): void{
this.hdpiManager.zoomModifier = this._saveZoom;
this.applyNewSize();
}
/** /**
* This is used to scale back the ui components to counter-act the zoom. * This is used to scale back the ui components to counter-act the zoom.
*/ */

View File

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

View File

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

View File

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

View File

@ -209,10 +209,14 @@ function createVideoConstraintStore() {
return { return {
subscribe, subscribe,
setDeviceId: (deviceId: string) => update((constraints) => { setDeviceId: (deviceId: string|undefined) => update((constraints) => {
constraints.deviceId = { if (deviceId !== undefined) {
exact: deviceId constraints.deviceId = {
}; exact: deviceId
};
} else {
delete constraints.deviceId;
}
return constraints; return constraints;
}), }),
@ -241,15 +245,19 @@ function createAudioConstraintStore() {
return { return {
subscribe, subscribe,
setDeviceId: (deviceId: string) => update((constraints) => { setDeviceId: (deviceId: string|undefined) => update((constraints) => {
selectedDeviceId = deviceId; selectedDeviceId = deviceId;
if (typeof(constraints) === 'boolean') { if (typeof(constraints) === 'boolean') {
constraints = {} constraints = {}
} }
constraints.deviceId = { if (deviceId !== undefined) {
exact: selectedDeviceId constraints.deviceId = {
}; exact: selectedDeviceId
};
} else {
delete constraints.deviceId;
}
return constraints; return constraints;
}) })
@ -410,13 +418,15 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'), error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'),
constraints constraints
}); });
return;
} else { } else {
//throw new Error('Unable to access your camera or microphone. Your browser is too old.'); //throw new Error('Unable to access your camera or microphone. Your browser is too old.');
set({ set({
type: 'error', type: 'error',
error: new Error('Unable to access your camera or microphone. Your browser is too old.'), error: new Error('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.'),
constraints constraints
}); });
return;
} }
} }
@ -508,3 +518,79 @@ export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStr
return $localStreamStore.constraints; return $localStreamStore.constraints;
}); });
/**
* Device list
*/
export const deviceListStore = readable<MediaDeviceInfo[]>([], function start(set) {
let deviceListCanBeQueried = false;
const queryDeviceList = () => {
// Note: so far, we are ignoring any failures.
navigator.mediaDevices.enumerateDevices().then((mediaDeviceInfos) => {
set(mediaDeviceInfos);
}).catch((e) => {
console.error(e);
throw e;
});
};
const unsubscribe = localStreamStore.subscribe((streamResult) => {
if (streamResult.type === "success" && streamResult.stream !== null) {
if (deviceListCanBeQueried === false) {
queryDeviceList();
deviceListCanBeQueried = true;
}
}
});
if (navigator.mediaDevices) {
navigator.mediaDevices.addEventListener('devicechange', queryDeviceList);
}
return function stop() {
unsubscribe();
if (navigator.mediaDevices) {
navigator.mediaDevices.removeEventListener('devicechange', queryDeviceList);
}
};
});
export const cameraListStore = derived(deviceListStore, ($deviceListStore) => {
return $deviceListStore.filter(device => device.kind === 'videoinput');
});
export const microphoneListStore = derived(deviceListStore, ($deviceListStore) => {
return $deviceListStore.filter(device => device.kind === 'audioinput');
});
// TODO: detect the new webcam and automatically switch on it.
cameraListStore.subscribe((devices) => {
// If the selected camera is unplugged, let's remove the constraint on deviceId
const constraints = get(videoConstraintStore);
if (!constraints.deviceId) {
return;
}
// If we cannot find the device ID, let's remove it.
// @ts-ignore
if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) {
videoConstraintStore.setDeviceId(undefined);
}
});
microphoneListStore.subscribe((devices) => {
// If the selected camera is unplugged, let's remove the constraint on deviceId
const constraints = get(audioConstraintStore);
if (typeof constraints === 'boolean') {
return;
}
if (!constraints.deviceId) {
return;
}
// If we cannot find the device ID, let's remove it.
// @ts-ignore
if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) {
audioConstraintStore.setDeviceId(undefined);
}
});

View File

@ -126,7 +126,7 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
let currentStreamPromise: Promise<MediaStream>; let currentStreamPromise: Promise<MediaStream>;
if (navigator.getDisplayMedia) { if (navigator.getDisplayMedia) {
currentStreamPromise = navigator.getDisplayMedia({constraints}); currentStreamPromise = navigator.getDisplayMedia({constraints});
} else if (navigator.mediaDevices.getDisplayMedia) { } else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints}); currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints});
} else { } else {
stopScreenSharing(); stopScreenSharing();
@ -183,7 +183,7 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
* A store containing whether the screen sharing button should be displayed or hidden. * A store containing whether the screen sharing button should be displayed or hidden.
*/ */
export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set) => { export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set) => {
if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) { if (!navigator.getDisplayMedia && (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia)) {
set(false); set(false);
return; return;
} }

View File

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

View File

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

View File

@ -0,0 +1,22 @@
import { writable } from "svelte/store";
/**
* A store that contains the URL of the sound currently playing
*/
function createSoundPlayingStore() {
const { subscribe, set, update } = writable<string|null>(null);
return {
subscribe,
playSound: (url: string) => {
set(url);
},
soundEnded: () => {
set(null);
}
};
}
export const soundPlayingStore = createSoundPlayingStore();

View File

@ -8,32 +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";
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
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: MediaTrackConstraints = {
// TODO: Make these values configurable in the game settings menu and store them in local storage
autoGainControl: false,
echoCancellation: true,
noiseSuppression: true
};
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;
@ -43,18 +22,14 @@ 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>();
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>();
private focused : boolean = true; private focused : boolean = true;
@ -88,76 +63,27 @@ export class MediaManager {
localStreamStore.subscribe((result) => { localStreamStore.subscribe((result) => {
if (result.type === 'error') { if (result.type === 'error') {
console.error(result.error); console.error(result.error);
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => { layoutManager.addInformation('warning', 'Camera access denied. Click here and check your browser permissions.', () => {
this.showHelpCameraSettingsCallBack(); helpCameraSettingsVisibleStore.set(true);
}, 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);
layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => { layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check your browser permissions.', () => {
this.showHelpCameraSettingsCallBack(); helpCameraSettingsVisibleStore.set(true);
}, this.userInputManager); }, this.userInputManager);
return; return;
} }
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;
} }
}); });
@ -176,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');
@ -236,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
@ -539,16 +395,6 @@ export class MediaManager {
this.showReportModalCallBacks.add(callback); this.showReportModalCallBacks.add(callback);
} }
public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){
this.helpCameraSettingsCallBacks.add(callback);
}
private showHelpCameraSettingsCallBack(){
for(const callBack of this.helpCameraSettingsCallBacks){
callBack();
}
}
//FIX ME SOUNDMETER: check stalability of sound meter calculation //FIX ME SOUNDMETER: check stalability of sound meter calculation
/*updateSoudMeter(){ /*updateSoudMeter(){
try{ try{
@ -594,12 +440,32 @@ export class MediaManager {
public getNotification(){ public getNotification(){
//Get notification //Get notification
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") { if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
Notification.requestPermission().catch((err) => { if (this.checkNotificationPromise()) {
console.error(`Notification permission error`, err); Notification.requestPermission().catch((err) => {
}); console.error(`Notification permission error`, err);
});
} else {
Notification.requestPermission();
}
} }
} }
/**
* Return true if the browser supports the modern version of the Notification API (which is Promise based) or false
* if we are on Safari...
*
* See https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
*/
private checkNotificationPromise(): boolean {
try {
Notification.requestPermission().then();
} catch(e) {
return false;
}
return true;
}
public createNotification(userName: string){ public createNotification(userName: string){
if(this.focused){ if(this.focused){
return; return;

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,
@ -60,6 +60,7 @@ export class ScreenSharingPeer extends Peer {
const message = JSON.parse(chunk.toString('utf8')); const message = JSON.parse(chunk.toString('utf8'));
if (message.streamEnded !== true) { if (message.streamEnded !== true) {
console.error('Unexpected message on screen sharing peer connection'); console.error('Unexpected message on screen sharing peer connection');
return;
} }
mediaManager.removeActiveScreenSharingVideo("" + this.userId); mediaManager.removeActiveScreenSharingVideo("" + this.userId);
}); });
@ -81,7 +82,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 +144,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) {
@ -234,7 +263,7 @@ export class SimplePeer {
const userIndex = this.Users.findIndex(user => user.userId === userId); const userIndex = this.Users.findIndex(user => user.userId === userId);
if(userIndex < 0){ if(userIndex < 0){
throw 'Couln\'t delete user'; throw 'Couldn\'t delete user';
} else { } else {
this.Users.splice(userIndex, 1); this.Users.splice(userIndex, 1);
} }
@ -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

@ -13,7 +13,6 @@ import WebFontLoaderPlugin from 'phaser3-rex-plugins/plugins/webfontloader-plugi
import {EntryScene} from "./Phaser/Login/EntryScene"; import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene"; import {MenuScene} from "./Phaser/Menu/MenuScene";
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
import {localUserStore} from "./Connexion/LocalUserStore"; import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
import {iframeListener} from "./Api/IframeListener"; import {iframeListener} from "./Api/IframeListener";
@ -96,7 +95,7 @@ const config: GameConfig = {
ErrorScene, ErrorScene,
CustomizeScene, CustomizeScene,
MenuScene, MenuScene,
HelpCameraSettingsScene], ],
//resolution: window.devicePixelRatio / 2, //resolution: window.devicePixelRatio / 2,
fps: fps, fps: fps,
dom: { dom: {
@ -152,7 +151,9 @@ iframeListener.init();
const app = new App({ const app = new App({
target: HtmlUtils.getElementByIdOrFail('svelte-overlay'), target: HtmlUtils.getElementByIdOrFail('svelte-overlay'),
props: { }, props: {
game: game
},
}) })
export default app export default app

View File

@ -1 +1,9 @@
@import "~@fontsource/press-start-2p/index.css"; @import "~@fontsource/press-start-2p/index.css";
*{
font-family: PixelFont-7,monospace;
}
.nes-btn {
font-family: "Press Start 2P";
}

View File

@ -1,5 +1,5 @@
@import "cowebsite.scss"; @import "cowebsite.scss";
@import "cowebsite-mobile.scss"; @import "cowebsite-mobile.scss";
@import "style.css"; @import "style";
@import "mobile-style.scss"; @import "mobile-style.scss";
@import "fonts.scss"; @import "fonts.scss";

View File

@ -136,6 +136,7 @@ body .message-info.warning{
.video-container.div-myCamVideo{ .video-container.div-myCamVideo{
border: none; border: none;
background-color: transparent;
} }
.div-myCamVideo { .div-myCamVideo {
@ -152,6 +153,8 @@ body .message-info.warning{
} }
video.myCamVideo{ video.myCamVideo{
background-color: #00000099;
max-height: 20vh;
width: 15vw; width: 15vw;
-webkit-transform: scaleX(-1); -webkit-transform: scaleX(-1);
transform: scaleX(-1); transform: scaleX(-1);
@ -363,39 +366,6 @@ video.myCamVideo{
} }
} }
.webrtcsetup{
display: none;
position: absolute;
top: 140px;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
height: 50%;
width: 50%;
border: white 6px solid;
}
.webrtcsetup .background-img {
position: relative;
display: block;
width: 40%;
height: 60%;
margin-left: auto;
margin-right: auto;
top: 50%;
transform: translateY(-50%);
}
#myCamVideoSetup {
width: 100%;
height: 100%;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}
.webrtcsetup.active{
display: block;
}
/* New layout */ /* New layout */
body { body {
margin: 0; margin: 0;
@ -909,35 +879,6 @@ input[type=range]:focus::-ms-fill-upper {
} }
/*audio html when audio message playing*/
.main-container .audio-playing {
position: absolute;
width: 200px;
height: 54px;
right: -210px;
top: 40px;
transition: all 0.1s ease-out;
background-color: black;
border-radius: 30px 0 0 30px;
display: inline-flex;
}
.main-container .audio-playing.active{
right: 0;
}
.main-container .audio-playing img{
/*width: 30px;*/
border-radius: 50%;
background-color: #ffda01;
padding: 10px;
}
.main-container .audio-playing p{
color: white;
margin-left: 10px;
margin-top: 14px;
}
/* VIDEO QUALITY */ /* VIDEO QUALITY */
.main-console div.setting h1{ .main-console div.setting h1{
color: white; color: white;
@ -1313,4 +1254,22 @@ div.action.danger p.action-body{
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
& > div {
position: relative;
width: 100%;
height: 100%;
& > div {
position: absolute;
width: 100%;
height: 100%;
}
& > div.scrollable {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
pointer-events: auto;
}
}
} }

View File

@ -7,6 +7,7 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import sveltePreprocess from 'svelte-preprocess'; import sveltePreprocess from 'svelte-preprocess';
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'; import NodePolyfillPlugin from 'node-polyfill-webpack-plugin';
import {DISPLAY_TERMS_OF_USE} from "./src/Enum/EnvironmentVariable";
const mode = process.env.NODE_ENV ?? 'development'; const mode = process.env.NODE_ENV ?? 'development';
const isProduction = mode === 'production'; const isProduction = mode === 'production';
@ -88,7 +89,16 @@ module.exports = {
preprocess: sveltePreprocess({ preprocess: sveltePreprocess({
scss: true, scss: true,
sass: true, sass: true,
}) }),
onwarn: function (warning: { code: string }, handleWarning: (warning: { code: string }) => void) {
// See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782
if (warning.code === 'a11y-no-onchange') { return }
if (warning.code === 'a11y-autofocus') { return }
// process as usual
handleWarning(warning);
}
} }
} }
}, },
@ -175,7 +185,8 @@ module.exports = {
'JITSI_PRIVATE_MODE': null, 'JITSI_PRIVATE_MODE': null,
'START_ROOM_URL': null, 'START_ROOM_URL': null,
'MAX_USERNAME_LENGTH': 8, 'MAX_USERNAME_LENGTH': 8,
'MAX_PER_GROUP': 4 'MAX_PER_GROUP': 4,
'DISPLAY_TERMS_OF_USE': false,
}) })
], ],

View File

@ -30,6 +30,13 @@
chalk "^2.0.0" chalk "^2.0.0"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/runtime@^7.14.0":
version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
dependencies:
regenerator-runtime "^0.13.4"
"@discoveryjs/json-ext@^0.5.0": "@discoveryjs/json-ext@^0.5.0":
version "0.5.3" version "0.5.3"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d"
@ -783,6 +790,14 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
automation-events@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/automation-events/-/automation-events-4.0.1.tgz#93acef8a457cbea65f16fdcef8f349fd2fafe298"
integrity sha512-8bQx+PVtNDMD5F2H40cQs7oexZve3Z0xC9fWRQT4fltF65f/WsSpjM4jpulL4K4yLLB71oi4/xVJJCJ5I/Kjbw==
dependencies:
"@babel/runtime" "^7.14.0"
tslib "^2.2.0"
available-typed-arrays@^1.0.2: available-typed-arrays@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
@ -4331,6 +4346,11 @@ rechoir@^0.7.0:
dependencies: dependencies:
resolve "^1.9.0" resolve "^1.9.0"
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
regex-not@^1.0.0, regex-not@^1.0.2: regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@ -4895,6 +4915,15 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
standardized-audio-context@^25.2.4:
version "25.2.4"
resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.2.4.tgz#d64dbdd70615171ec90d1b7151a0d945af94cf3d"
integrity sha512-uQKZXRnXrPVO1V6SwZ7PiV3RkQqRY3n7i6Q8nbTXYvoz8NftRNzfOIlwefpuC8LVLUUs9dhbKTpP+WOA82dkBw==
dependencies:
"@babel/runtime" "^7.14.0"
automation-events "^4.0.1"
tslib "^2.2.0"
static-extend@^0.1.1: static-extend@^0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@ -5225,7 +5254,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3: tslib@^2.0.3, tslib@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
@ -5639,9 +5668,9 @@ wrappy@1:
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^6.2.1: ws@^6.2.1:
version "6.2.1" version "6.2.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
dependencies: dependencies:
async-limiter "~1.0.0" async-limiter "~1.0.0"

View File

@ -0,0 +1,164 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51],
"height":10,
"id":3,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":1,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"floorLayer",
"objects":[
{
"height":254.57168784029,
"id":1,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":12,
"text":"Test 1 : \nBlock permission to camera and\/or microphone access.\n\nResult 1 :\nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n\nTest 2 : \nReload the page and block permission to camera and\/or microphone access on the camera setting page.\n\nResult 2 : \nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n",
"wrap":true
},
"type":"",
"visible":true,
"width":295.278811252269,
"x":12.2517014519056,
"y":49.3021778584392
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":6,
"nextobjectid":2,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":32,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"Validation\/tileset_dungeon.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"dungeon",
"spacing":0,
"tilecount":64,
"tileheight":32,
"tiles":[
{
"id":0,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":1,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":2,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":5,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":8,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":10,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":16,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":17,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":18,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.4,
"width":10
}

View File

@ -82,6 +82,14 @@
<a href="#" class="testLink" data-testmap="energy.json" target="_blank">Test energy consumption</a> <a href="#" class="testLink" data-testmap="energy.json" target="_blank">Test energy consumption</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-help-camera-setting"> Success <input type="radio" name="test-help-camera-setting"> Failure <input type="radio" name="test-help-camera-setting" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="help_camera_setting.json" target="_blank">Test the HelpCameraSettingScene</a>
</td>
</tr>
</table> </table>
<script> <script>

View File

@ -110,12 +110,10 @@ export class AuthenticateController extends BaseController {
private anonymLogin(){ private anonymLogin(){
this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { this.App.options("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.end(); res.end();
}); });
this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => { this.App.post("/anonymLogin", (res: HttpResponse, req: HttpRequest) => {
res.onAborted(() => { res.onAborted(() => {
console.warn('Login request was aborted'); console.warn('Login request was aborted');
}) })