Merge pull request #825 from workadventure-xce/audio_player_improvements

Audio player improvements
This commit is contained in:
Kharhamel 2021-03-23 14:07:20 +01:00 committed by GitHub
commit 7765775df5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 66 deletions

View File

@ -92,12 +92,12 @@
</svg> </svg>
</button> </button>
<div class="audioplayer"> <div class="audioplayer">
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.05" value="1" /> <input type="range" id="audioplayer_volume" min="0" max="1" step="0.025" value="1" />
</div> </div>
</div> </div>
<div class="audioplayer"> <div class="audioplayer">
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations"> <label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
autoreduce reduce in conversations
<input type="checkbox" id="audioplayer_decrease_while_talking" checked /> <input type="checkbox" id="audioplayer_decrease_while_talking" checked />
</label> </label>
<div id="audioplayer" style="visibility: hidden"></div> <div id="audioplayer" style="visibility: hidden"></div>

View File

@ -363,10 +363,14 @@ body {
justify-content: center; justify-content: center;
} }
.audioplayer > div {
padding-right: 1.2rem;
}
#audioplayerctrl { #audioplayerctrl {
position: fixed; position: fixed;
top: 0; top: 0;
right: 50%; right: calc(50% - 120px);
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
color: white; color: white;
transition: transform 0.5s; transition: transform 0.5s;

View File

@ -1,12 +1,15 @@
import {LocalUser} from "./LocalUser"; import {LocalUser} from "./LocalUser";
const characterLayersKey = 'characterLayers'; const playerNameKey = 'playerName';
const gameQualityKey = 'gameQuality'; const selectedPlayerKey = 'selectedPlayer';
const videoQualityKey = 'videoQuality'; const customCursorPositionKey = 'customCursorPosition';
const characterLayersKey = 'characterLayers';
const gameQualityKey = 'gameQuality';
const videoQualityKey = 'videoQuality';
const audioPlayerVolumeKey = 'audioVolume';
const audioPlayerMuteKey = 'audioMute';
//todo: add localstorage fallback
class LocalUserStore { class LocalUserStore {
saveUser(localUser: LocalUser) { saveUser(localUser: LocalUser) {
localStorage.setItem('localUser', JSON.stringify(localUser)); localStorage.setItem('localUser', JSON.stringify(localUser));
} }
@ -14,48 +17,62 @@ class LocalUserStore {
const data = localStorage.getItem('localUser'); const data = localStorage.getItem('localUser');
return data ? JSON.parse(data) : null; return data ? JSON.parse(data) : null;
} }
setName(name:string): void { setName(name:string): void {
window.localStorage.setItem('playerName', name); localStorage.setItem(playerNameKey, name);
} }
getName(): string { getName(): string {
return window.localStorage.getItem('playerName') ?? ''; return localStorage.getItem(playerNameKey) || '';
} }
setPlayerCharacterIndex(playerCharacterIndex: number): void { setPlayerCharacterIndex(playerCharacterIndex: number): void {
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex); localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
} }
getPlayerCharacterIndex(): number { getPlayerCharacterIndex(): number {
return parseInt(window.localStorage.getItem('selectedPlayer') || ''); return parseInt(localStorage.getItem(selectedPlayerKey) || '');
} }
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void { setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers})); localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
} }
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null { getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null"); return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
} }
setCharacterLayers(layers: string[]): void { setCharacterLayers(layers: string[]): void {
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers)); localStorage.setItem(characterLayersKey, JSON.stringify(layers));
} }
getCharacterLayers(): string[]|null { getCharacterLayers(): string[]|null {
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null"); return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
}
getGameQualityValue(): number {
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
} }
setGameQualityValue(value: number): void { setGameQualityValue(value: number): void {
localStorage.setItem(gameQualityKey, '' + value); localStorage.setItem(gameQualityKey, '' + value);
} }
getGameQualityValue(): number {
getVideoQualityValue(): number { return parseInt(localStorage.getItem(gameQualityKey) || '60');
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
} }
setVideoQualityValue(value: number): void { setVideoQualityValue(value: number): void {
localStorage.setItem(videoQualityKey, '' + value); localStorage.setItem(videoQualityKey, '' + value);
} }
getVideoQualityValue(): number {
return parseInt(localStorage.getItem(videoQualityKey) || '20');
}
setAudioPlayerVolume(value: number): void {
localStorage.setItem(audioPlayerVolumeKey, '' + value);
}
getAudioPlayerVolume(): number {
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
}
setAudioPlayerMuted(value: boolean): void {
localStorage.setItem(audioPlayerMuteKey, value.toString());
}
getAudioPlayerMuted(): boolean {
return localStorage.getItem(audioPlayerMuteKey) === 'true';
}
} }
export const localUserStore = new LocalUserStore(); export const localUserStore = new LocalUserStore();

View File

@ -29,7 +29,9 @@ import {
ON_ACTION_TRIGGER_BUTTON, ON_ACTION_TRIGGER_BUTTON,
TRIGGER_JITSI_PROPERTIES, TRIGGER_JITSI_PROPERTIES,
TRIGGER_WEBSITE_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES WEBSITE_MESSAGE_PROPERTIES,
AUDIO_VOLUME_PROPERTY,
AUDIO_LOOP_PROPERTY
} from "../../WebRtc/LayoutManager"; } from "../../WebRtc/LayoutManager";
import {GameMap} from "./GameMap"; import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
@ -455,16 +457,12 @@ export class GameScene extends ResizableScene implements CenterListener {
}); });
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
audioManager.decreaseVolume();
this.shareGroupPosition(groupPositionMessage); this.shareGroupPosition(groupPositionMessage);
this.openChatIcon.setVisible(true);
}) })
this.connection.onGroupDeleted((groupId: number) => { this.connection.onGroupDeleted((groupId: number) => {
audioManager.restoreVolume();
try { try {
this.deleteGroup(groupId); this.deleteGroup(groupId);
this.openChatIcon.setVisible(false);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -517,11 +515,15 @@ export class GameScene extends ResizableScene implements CenterListener {
onConnect(user: UserSimplePeerInterface) { onConnect(user: UserSimplePeerInterface) {
self.presentationModeSprite.setVisible(true); self.presentationModeSprite.setVisible(true);
self.chatModeSprite.setVisible(true); self.chatModeSprite.setVisible(true);
self.openChatIcon.setVisible(true);
audioManager.decreaseVolume();
}, },
onDisconnect(userId: number) { onDisconnect(userId: number) {
if (self.simplePeer.getNbConnections() === 0) { if (self.simplePeer.getNbConnections() === 0) {
self.presentationModeSprite.setVisible(false); self.presentationModeSprite.setVisible(false);
self.chatModeSprite.setVisible(false); self.chatModeSprite.setVisible(false);
self.openChatIcon.setVisible(false);
audioManager.restoreVolume();
} }
} }
}) })
@ -662,14 +664,15 @@ export class GameScene extends ResizableScene implements CenterListener {
this.connection.setSilent(true); this.connection.setSilent(true);
} }
}); });
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => { this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl()); const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined;
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined;
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
}); });
// TODO: This legacy property should be removed at some point
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => { this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl()); newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
}); });
} }
private getMapDirUrl(): string { private getMapDirUrl(): string {

View File

@ -1,5 +1,6 @@
import {HtmlUtils} from "./HtmlUtils"; import {HtmlUtils} from "./HtmlUtils";
import {isUndefined} from "generic-type-guard"; import {isUndefined} from "generic-type-guard";
import {localUserStore} from "../Connexion/LocalUserStore";
enum audioStates { enum audioStates {
closed = 0, closed = 0,
@ -9,6 +10,8 @@ enum audioStates {
const audioPlayerDivId = "audioplayer"; const audioPlayerDivId = "audioplayer";
const audioPlayerCtrlId = "audioplayerctrl"; const audioPlayerCtrlId = "audioplayerctrl";
const audioPlayerVolId = "audioplayer_volume";
const audioPlayerMuteId = "audioplayer_volume_icon_playing";
const animationTime = 500; const animationTime = 500;
class AudioManager { class AudioManager {
@ -17,6 +20,8 @@ class AudioManager {
private audioPlayerDiv: HTMLDivElement; private audioPlayerDiv: HTMLDivElement;
private audioPlayerCtrl: HTMLDivElement; private audioPlayerCtrl: HTMLDivElement;
private audioPlayerElem: HTMLAudioElement | undefined; private audioPlayerElem: HTMLAudioElement | undefined;
private audioPlayerVol: HTMLInputElement;
private audioPlayerMute: HTMLInputElement;
private volume = 1; private volume = 1;
private muted = false; private muted = false;
@ -26,19 +31,19 @@ class AudioManager {
constructor() { constructor() {
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId); this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId); this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
this.audioPlayerVol = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerVolId);
this.audioPlayerMute = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerMuteId);
const storedVolume = localStorage.getItem('volume') this.volume = localUserStore.getAudioPlayerVolume();
if (storedVolume === null) { this.audioPlayerVol.value = '' + this.volume;
this.setVolume(1);
} else { this.muted = localUserStore.getAudioPlayerMuted();
this.volume = parseFloat(storedVolume); if (this.muted) {
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = storedVolume; this.audioPlayerMute.classList.add('muted');
} }
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
} }
public playAudio(url: string|number|boolean, mapDirUrl: string, loop=false): void { public playAudio(url: string|number|boolean, mapDirUrl: string, volume: number|undefined, loop=false): void {
const audioPath = url as string; const audioPath = url as string;
let realAudioPath = ''; let realAudioPath = '';
@ -50,7 +55,7 @@ class AudioManager {
realAudioPath = mapDirUrl + '/' + url; realAudioPath = mapDirUrl + '/' + url;
} }
this.loadAudio(realAudioPath); this.loadAudio(realAudioPath, volume);
if (loop) { if (loop) {
this.loop(); this.loop();
@ -75,26 +80,29 @@ class AudioManager {
} }
private changeVolume(talking = false): void { private changeVolume(talking = false): void {
if (!isUndefined(this.audioPlayerElem)) { if (isUndefined(this.audioPlayerElem)) {
this.audioPlayerElem.volume = this.naturalVolume(talking && this.decreaseWhileTalking); return;
this.audioPlayerElem.muted = this.muted;
} }
}
private naturalVolume(makeSofter: boolean = false): number { const reduceVolume = talking && this.decreaseWhileTalking;
const volume = this.volume if (reduceVolume && !this.volumeReduced) {
const retVol = makeSofter && !this.volumeReduced ? Math.pow(volume * 0.5, 3) : volume this.volume *= 0.5;
this.volumeReduced = makeSofter } else if (!reduceVolume && this.volumeReduced) {
return retVol; this.volume *= 2.0;
}
this.volumeReduced = reduceVolume;
this.audioPlayerElem.volume = this.volume;
this.audioPlayerVol.value = '' + this.volume;
this.audioPlayerElem.muted = this.muted;
} }
private setVolume(volume: number): void { private setVolume(volume: number): void {
this.volume = volume; this.volume = volume;
localStorage.setItem('volume', '' + volume); localUserStore.setAudioPlayerVolume(volume);
} }
private loadAudio(url: string, volume: number|undefined): void {
private loadAudio(url: string): void {
this.load(); this.load();
/* Solution 1, remove whole audio player */ /* Solution 1, remove whole audio player */
@ -112,23 +120,24 @@ class AudioManager {
this.audioPlayerElem.append(srcElem); this.audioPlayerElem.append(srcElem);
this.audioPlayerDiv.append(this.audioPlayerElem); this.audioPlayerDiv.append(this.audioPlayerElem);
this.volume = volume ? Math.min(volume, this.volume) : this.volume;
this.changeVolume(); this.changeVolume();
this.audioPlayerElem.play(); this.audioPlayerElem.play();
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute'); const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
muteElem.onclick = (ev: Event)=> { muteElem.onclick = (ev: Event) => {
this.muted = !this.muted; this.muted = !this.muted;
this.changeVolume(); this.changeVolume();
localUserStore.setAudioPlayerMuted(this.muted);
if (this.muted) { if (this.muted) {
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.add('muted'); this.audioPlayerMute.classList.add('muted');
} else { } else {
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.remove('muted'); this.audioPlayerMute.classList.remove('muted');
} }
} }
const volumeElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume'); this.audioPlayerVol.oninput = (ev: Event)=> {
volumeElem.oninput = (ev: Event)=> {
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value)); this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
this.changeVolume(); this.changeVolume();

View File

@ -31,6 +31,9 @@ export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage'; export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage'; export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
export const AUDIO_VOLUME_PROPERTY = 'audioVolume';
export const AUDIO_LOOP_PROPERTY = 'audioLoop';
/** /**
* This class is in charge of the video-conference layout. * This class is in charge of the video-conference layout.
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode. * It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.

View File

@ -63,7 +63,7 @@ export class SimplePeer {
} }
public getNbConnections(): number { public getNbConnections(): number {
return this.PeerConnectionArray.size; return this.Users.length;
} }
/** /**
@ -230,9 +230,6 @@ export class SimplePeer {
this.closeScreenSharingConnection(userId); this.closeScreenSharingConnection(userId);
for (const peerConnectionListener of this.peerConnectionListeners) {
peerConnectionListener.onDisconnect(userId);
}
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 'Couln\'t delete user';
@ -250,6 +247,10 @@ export class SimplePeer {
this.PeerScreenSharingConnectionArray.delete(userId); this.PeerScreenSharingConnectionArray.delete(userId);
} }
} }
for (const peerConnectionListener of this.peerConnectionListeners) {
peerConnectionListener.onDisconnect(userId);
}
} }
/** /**