Browse Source
- nodemon and webpack now basically work like my Gulp setup used to in DTP Base - Connecting to rooms and sending chat messages - Chat messages are rendered server-side and HTML is emitted to clients - Chat room messages are created with POST requests at /chat/room/:roomId/message - ChatAudio system added - SFX_CHAT_MESSAGE added and played when message is received - more socket management to guarantee pulling out of room when leaving - person-through-window added as "Leave room" icon to disconnectdevelop
15 changed files with 521 additions and 48 deletions
@ -0,0 +1,2 @@ |
|||||
|
include message |
||||
|
+renderChatMessage(message) |
@ -0,0 +1,151 @@ |
|||||
|
// chat-audio.js
|
||||
|
// Copyright (C) 2024 DTP Technologies, LLC
|
||||
|
// All Rights Reserved
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const DTP_COMPONENT_NAME = 'ChatAudio'; |
||||
|
|
||||
|
import DtpLog from 'lib/dtp-log'; |
||||
|
|
||||
|
const AudioContext = window.AudioContext || window.webkitAudioContext; |
||||
|
|
||||
|
export default class ChatAudio { |
||||
|
|
||||
|
constructor ( ) { |
||||
|
this.log = new DtpLog(DTP_COMPONENT_NAME); |
||||
|
} |
||||
|
|
||||
|
start ( ) { |
||||
|
this.log.info('start', 'starting Web Audio API main context'); |
||||
|
this.ctx = new AudioContext(); |
||||
|
|
||||
|
this.masterVolume = this.ctx.createGain(); |
||||
|
this.masterVolume.connect(this.ctx.destination); |
||||
|
|
||||
|
this.musicGain = this.ctx.createGain(); |
||||
|
this.musicGain.value = 0.7; |
||||
|
this.musicGain.connect(this.masterVolume); |
||||
|
|
||||
|
this.sounds = { }; |
||||
|
} |
||||
|
|
||||
|
async loadSound (soundId, url) { |
||||
|
if (this.sounds[soundId]) { |
||||
|
throw new Error('Already have sound registered for soundId'); |
||||
|
} |
||||
|
|
||||
|
const audioBuffer = await this.loadAudioBuffer(url); |
||||
|
const sound = { soundId, url, audioBuffer }; |
||||
|
this.sounds[soundId] = sound; |
||||
|
|
||||
|
return sound; |
||||
|
} |
||||
|
|
||||
|
hasSound (soundId) { |
||||
|
return !!this.sounds[soundId]; |
||||
|
} |
||||
|
|
||||
|
playSound (soundId, options) { |
||||
|
const sound = this.sounds[soundId]; |
||||
|
if (!sound) { |
||||
|
throw new Error(`Invalid soundId: ${soundId}`); |
||||
|
} |
||||
|
|
||||
|
this.log.info('playSound', 'playing sound', { soundId }); |
||||
|
return { soundId, ...this.playAudioBuffer(sound.audioBuffer, options) }; |
||||
|
} |
||||
|
|
||||
|
loadAudioBuffer (url) { |
||||
|
return new Promise(async (resolve, reject) => { |
||||
|
const response = await fetch(url); |
||||
|
const audioData = await response.arrayBuffer(); |
||||
|
this.ctx.decodeAudioData(audioData, resolve, reject); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
playAudioBuffer (buffer, options) { |
||||
|
options = Object.assign({ |
||||
|
gain: 0.4, |
||||
|
loop: false, |
||||
|
}, options); |
||||
|
|
||||
|
const source = this.ctx.createBufferSource(); |
||||
|
source.buffer = buffer; |
||||
|
source.loop = options.loop; |
||||
|
|
||||
|
const gainNode = this.ctx.createGain(); |
||||
|
gainNode.gain.value = options.gain; |
||||
|
|
||||
|
source.connect(gainNode); |
||||
|
gainNode.connect(this.masterVolume); |
||||
|
|
||||
|
source.start(); |
||||
|
|
||||
|
return { gain: gainNode, source }; |
||||
|
} |
||||
|
|
||||
|
setMusicStream (url) { |
||||
|
let source; |
||||
|
|
||||
|
this.stopMusicStream(); |
||||
|
this.log.debug('setMusicStream', 'setting new music stream', { url }); |
||||
|
this.music = new Audio(); |
||||
|
this.music.setAttribute('loop', 'loop'); |
||||
|
|
||||
|
source = document.createElement('source'); |
||||
|
source.setAttribute('src', `${url}.ogg`); |
||||
|
source.setAttribute('type', 'audio/ogg'); |
||||
|
this.music.appendChild(source); |
||||
|
|
||||
|
source = document.createElement('source'); |
||||
|
source.setAttribute('src', `${url}.mp3`); |
||||
|
source.setAttribute('type', 'audio/mp3'); |
||||
|
this.music.appendChild(source); |
||||
|
} |
||||
|
|
||||
|
playMusicStream ( ) { |
||||
|
if (this.musicSource) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.musicSource = this.ctx.createMediaElementSource(this.music); |
||||
|
this.musicSource.connect(this.musicGain); |
||||
|
|
||||
|
this.log.debug('playMusicStream', 'starting music stream playback'); |
||||
|
this.music.play(); |
||||
|
} |
||||
|
|
||||
|
stopMusicStream ( ) { |
||||
|
if (!this.musicSource) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.log.debug('pauseMusicStream', 'stopping music stream playback'); |
||||
|
this.music.pause(); |
||||
|
|
||||
|
this.musicGain.value = 0; |
||||
|
|
||||
|
this.musicSource.disconnect(this.musicGain); |
||||
|
delete this.musicSource; |
||||
|
} |
||||
|
|
||||
|
get musicVolume ( ) { return this.musicGain.gain.value; } |
||||
|
set musicVolume (volume) { |
||||
|
this.musicGain.gain.value = volume; |
||||
|
} |
||||
|
|
||||
|
get haveMusicStream ( ) { |
||||
|
return !!this.music && |
||||
|
!!this.musicSource && |
||||
|
!!this.musicGain |
||||
|
; |
||||
|
} |
||||
|
|
||||
|
get isMusicPaused ( ) { |
||||
|
if (!this.music) { |
||||
|
return true; |
||||
|
} |
||||
|
return this.music.paused; |
||||
|
} |
||||
|
} |
Binary file not shown.
@ -1,4 +1,9 @@ |
|||||
{ |
{ |
||||
"verbose": true, |
"verbose": true, |
||||
"ignore": ["dist"] |
"ignore": [ |
||||
|
"dist", |
||||
|
"client/**/*", |
||||
|
"lib/client/**/*", |
||||
|
"node_modules/**/*" |
||||
|
] |
||||
} |
} |
Loading…
Reference in new issue