diff --git a/src/audio/Audio.ts b/src/audio/Audio.ts new file mode 100644 index 0000000..f66443e --- /dev/null +++ b/src/audio/Audio.ts @@ -0,0 +1,83 @@ +import { Audio as StaticAudio, PositionalAudio, AudioLoader } from 'three' +import { defineComponent } from 'vue' +import Object3D from '../core/Object3D' +import { bindProp } from '../tools' + +export interface AudioSetupInterface { + streamMediaElement?: HTMLAudioElement +} + +type ConcreteAudio = StaticAudio | PositionalAudio + +export default defineComponent({ + extends: Object3D, + name: 'Audio', + props: { + src: String, + volume: { type: Number, default: 1.0 }, + isStreamed: { type: Boolean, default: true } + }, + setup (): AudioSetupInterface { + let streamMediaElement = new Audio(); + return { streamMediaElement } + }, + methods: { + initAudio(audio: ConcreteAudio) { + this.initObject3D(audio) + this.bindProps(audio) + this.loadAudioAndPlay(audio) + }, + bindProps(audio: ConcreteAudio) { + ['volume', 'isStreamed'].forEach(p => { + bindProp(this.$props, p, audio) + }) + }, + loadAudioAndPlay(audio: ConcreteAudio) { + if (!this.src) return undefined + + if (this.isStreamed) { + this.loadAudioFromStream(audio, this.src) + } else { + this.loadAudioFromMemory(audio, this.src) + } + }, + loadAudioFromMemory(audio:ConcreteAudio, src:string) { + const audioLoader = new AudioLoader(); + const instance = this + audioLoader.load( src, function( buffer ) { + audio.setBuffer( buffer ); + instance.play(audio) + }); + }, + loadAudioFromStream(audio:ConcreteAudio, src:string) { + this.streamMediaElement = new Audio(src); + this.streamMediaElement.loop = true; + this.streamMediaElement.preload = 'metadata'; + this.streamMediaElement.crossOrigin = 'anonymous'; + const instance = this + this.streamMediaElement.addEventListener("loadedmetadata", function(_event) { + console.log('loaded the stream') + instance.play(audio) + }); + audio.setMediaElementSource(this.streamMediaElement) + }, + play(audio:ConcreteAudio) { + if (this.isStreamed) { + if (!this.streamMediaElement) { return } + this.streamMediaElement.play() + } else { + audio.play() + } + }, + stop(audio:ConcreteAudio) { + if (this.isStreamed) { + if (!this.streamMediaElement) { return } + this.streamMediaElement.pause() + this.streamMediaElement.currentTime = 0; + } else { + audio.stop() + } + } + }, + __hmrId: 'Audio', +}) diff --git a/src/audio/AudioListener.ts b/src/audio/AudioListener.ts new file mode 100644 index 0000000..1da10b8 --- /dev/null +++ b/src/audio/AudioListener.ts @@ -0,0 +1,32 @@ +import { defineComponent, ComponentPublicInstance, InjectionKey, inject } from 'vue' +import { AudioListener } from 'three' +import Object3D from '../core/Object3D' +import { RendererInjectionKey } from './../core/Renderer' + +//TODO: currently the AudioListener is inheriting Object3D so it must be a +// child of Scene. However the AudioListener should actually be possible to be +// a child of Camera... Then again Camera is said to extend Object3D in the +// future... + +export interface AudioListenerSetupInterface { + audioListener?: AudioListener + } + +export default defineComponent({ + extends: Object3D, + name: 'AudioListener', + setup():AudioListenerSetupInterface { + return {} + }, + created() { + this.audioListener = new AudioListener() + this.initObject3D(this.audioListener) + const renderer = inject(RendererInjectionKey) + if (!renderer) { + console.error('Renderer not found') + return + } + renderer.audioListener = this.audioListener + }, + __hmrId: 'AudioListener', +}) diff --git a/src/audio/PositionalAudio.ts b/src/audio/PositionalAudio.ts new file mode 100644 index 0000000..3e274f6 --- /dev/null +++ b/src/audio/PositionalAudio.ts @@ -0,0 +1,39 @@ +import { defineComponent, inject } from 'vue' +import { PositionalAudio } from 'three' +import { RendererInjectionKey } from './../core/Renderer' +import Audio from './Audio' +import { bindProp } from '../tools' + +export default defineComponent({ + extends: Audio, + name: 'PositionalAudio', + props: { + refDistance: { type: Number, default: 1.0 }, + rolloffFactor: { type: Number, default: 1.0 } + }, + created() { + const renderer = inject(RendererInjectionKey) + + if (!renderer) { + console.error('Renderer not found') + return + } + + if (!renderer.audioListener) { + console.error("No AudioListener component found in the Renderer's child components tree!") + return + } + + const positionalAudio = new PositionalAudio(renderer.audioListener) + this.initAudio(positionalAudio) + this.bindProps(positionalAudio) + }, + methods: { + bindProps(audio: PositionalAudio) { + ['refDistance', 'rolloffFactor'].forEach(p => { + bindProp(this.$props, p, audio) + }) + }, + }, + __hmrId: 'AudioTest', +}) diff --git a/src/audio/StaticAudio.ts b/src/audio/StaticAudio.ts new file mode 100644 index 0000000..e6d3ab2 --- /dev/null +++ b/src/audio/StaticAudio.ts @@ -0,0 +1,26 @@ +import { defineComponent, inject } from 'vue' +import { Audio as StaticAudio } from 'three' +import { RendererInjectionKey } from './../core/Renderer' +import Audio from './Audio' + +export default defineComponent({ + extends: Audio, + name: 'StaticAudio', + created() { + const renderer = inject(RendererInjectionKey) + + if (!renderer) { + console.error('Renderer not found') + return + } + + if (!renderer.audioListener) { + console.error("No AudioListener component found in the Renderer's child components tree!") + return + } + + const staticAudio = new StaticAudio(renderer.audioListener) + this.initAudio( staticAudio ) + }, + __hmrId: 'StaticAudio', +}) diff --git a/src/audio/index.ts b/src/audio/index.ts new file mode 100644 index 0000000..0bbc0fa --- /dev/null +++ b/src/audio/index.ts @@ -0,0 +1,3 @@ +export { default as AudioListener } from './AudioListener' +export { default as PositionalAudio } from './PositionalAudio' +export { default as StaticAudio } from './StaticAudio' diff --git a/src/core/Renderer.ts b/src/core/Renderer.ts index d741d5c..2d34c08 100644 --- a/src/core/Renderer.ts +++ b/src/core/Renderer.ts @@ -1,5 +1,5 @@ /* eslint-disable no-use-before-define */ -import { Camera, Scene, WebGLRenderer, WebGLRendererParameters } from 'three' +import { Camera, Scene, WebGLRenderer, WebGLRendererParameters, AudioListener } from 'three' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer' import { ComponentPublicInstance, defineComponent, InjectionKey, PropType, watchEffect } from 'vue' import { bindObjectProp } from '../tools' @@ -75,6 +75,7 @@ export interface RendererInterface extends RendererSetupInterface { scene?: Scene camera?: Camera composer?: EffectComposer + audioListener?: AudioListener onInit(cb: InitCallbackType): void onMounted(cb: MountedCallbackType): void @@ -181,6 +182,10 @@ export default defineComponent({ get: function(): EffectComposer | undefined { return this.three.composer }, set: function(composer: EffectComposer): void { this.three.composer = composer }, }, + audioListener: { + get: function(): AudioListener | undefined { return this.three.audioListener }, + set: function(audioListener: AudioListener): void { this.three.audioListener = audioListener }, + } }, provide() { return { diff --git a/src/core/useThree.ts b/src/core/useThree.ts index 61761f6..a076e43 100644 --- a/src/core/useThree.ts +++ b/src/core/useThree.ts @@ -1,4 +1,4 @@ -import { Camera, Object3D, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer, WebGLRendererParameters } from 'three' +import { Camera, Object3D, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer, WebGLRendererParameters, AudioListener } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' import usePointer, { PointerConfigInterface, PointerPublicConfigInterface, PointerInterface } from './usePointer' @@ -32,6 +32,7 @@ export interface ThreeInterface { composer?: EffectComposer camera?: Camera cameraCtrl?: OrbitControls + audioListener?: AudioListener scene?: Scene pointer?: PointerInterface size: SizeInterface diff --git a/src/index.ts b/src/index.ts index 7087925..23ddc4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export * from './materials/index' export * from './meshes/index' export * from './models/index' export * from './effects/index' +export * from './audio/index' // export * from './components/index' diff --git a/src/plugin.ts b/src/plugin.ts index 5c3fbac..16a2251 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -77,6 +77,10 @@ export const TroisJSVuePlugin = { 'TiltShiftPass', 'UnrealBloomPass', 'ZoomBlurPass', + + 'AudioListener', + 'PositionalAudio', + 'StaticAudio' ] comps.forEach(comp => {