sound proximity
This commit is contained in:
parent
7523465c0b
commit
14a519b705
139
demo/music.html
139
demo/music.html
|
|
@ -62,41 +62,6 @@
|
|||
</video>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="415" y="295" width="166" height="200">
|
||||
<video loop>
|
||||
<source src="/dancing-flower.mov" type="video/quicktime" />
|
||||
<source src="/dancing-flower.webm" type="video/webm" />
|
||||
</video>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="20" y="525" width="166" height="200">
|
||||
<video loop>
|
||||
<source src="/dancing-flower.mov" type="video/quicktime" />
|
||||
<source src="/dancing-flower.webm" type="video/webm" />
|
||||
</video>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="160" y="545" width="166" height="200">
|
||||
<video loop>
|
||||
<source src="/dancing-flower.mov" type="video/quicktime" />
|
||||
<source src="/dancing-flower.webm" type="video/webm" />
|
||||
</video>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="290" y="530" width="166" height="200">
|
||||
<video loop>
|
||||
<source src="/dancing-flower.mov" type="video/quicktime" />
|
||||
<source src="/dancing-flower.webm" type="video/webm" />
|
||||
</video>
|
||||
</spatial-geometry>
|
||||
|
||||
<spatial-geometry x="420" y="520" width="166" height="200">
|
||||
<video loop>
|
||||
<source src="/dancing-flower.mov" type="video/quicktime" />
|
||||
<source src="/dancing-flower.webm" type="video/webm" />
|
||||
</video>
|
||||
</spatial-geometry>
|
||||
|
||||
<script type="module">
|
||||
import { SpatialGeometry } from '../src/canvas/spatial-geometry.ts';
|
||||
import { RecordPlayer } from '../src/music/record-player.ts';
|
||||
|
|
@ -104,28 +69,112 @@
|
|||
SpatialGeometry.register();
|
||||
RecordPlayer.register();
|
||||
|
||||
const flowers = Array.from(document.querySelectorAll('video'));
|
||||
const audio = document.querySelector('audio');
|
||||
|
||||
let proximityDistance = 200;
|
||||
const proximitySet = new Set();
|
||||
const recordPlayerGeometry = document.querySelector('spatial-geometry:has(record-player)');
|
||||
const recordPlayer = recordPlayerGeometry.firstElementChild;
|
||||
const flowers = document.querySelectorAll('spatial-geometry:has(video)');
|
||||
// set playback rate when video is ready
|
||||
function setPlayback(e) {
|
||||
e.target.playbackRate = (91 / 60) * e.target.duration;
|
||||
e.target.removeEventListener('canplay', setPlayback);
|
||||
}
|
||||
|
||||
flowers.forEach((el) => {
|
||||
el.addEventListener('canplay', setPlayback);
|
||||
el.firstElementChild.addEventListener('canplay', setPlayback);
|
||||
});
|
||||
|
||||
audio.addEventListener('play', () => {
|
||||
setTimeout(() => flowers.forEach((el) => el.play()), 1200);
|
||||
function collisionDetection(rect1, rect2) {
|
||||
return (
|
||||
rect1.left < rect2.right &&
|
||||
rect1.right > rect2.left &&
|
||||
rect1.top < rect2.bottom &&
|
||||
rect1.bottom > rect2.top
|
||||
);
|
||||
}
|
||||
|
||||
function proximityDetection(rect1, rect2, distance = 30) {
|
||||
return collisionDetection(
|
||||
DOMRectReadOnly.fromRect({
|
||||
x: rect1.x - distance,
|
||||
y: rect1.y - distance,
|
||||
height: rect1.height + distance * 2,
|
||||
width: rect1.width + distance * 2,
|
||||
}),
|
||||
rect2
|
||||
);
|
||||
}
|
||||
|
||||
function updateFlowerProximity(flower) {
|
||||
const alreadyIntersection = proximitySet.has(flower);
|
||||
// TODO: refactor this hack once resizing and the vertices API are figured out
|
||||
const isNowIntersecting = proximityDetection(
|
||||
DOMRectReadOnly.fromRect({
|
||||
x: recordPlayerGeometry.x,
|
||||
y: recordPlayerGeometry.y,
|
||||
height: recordPlayerGeometry.height,
|
||||
width: recordPlayerGeometry.width,
|
||||
}),
|
||||
DOMRectReadOnly.fromRect({
|
||||
x: flower.x,
|
||||
y: flower.y,
|
||||
height: flower.height,
|
||||
width: flower.width,
|
||||
}),
|
||||
proximityDistance
|
||||
);
|
||||
|
||||
const video = flower.firstElementChild;
|
||||
if (isNowIntersecting && !alreadyIntersection) {
|
||||
proximitySet.add(flower);
|
||||
if (!recordPlayer.paused) {
|
||||
video.play();
|
||||
}
|
||||
} else if (alreadyIntersection && !isNowIntersecting) {
|
||||
proximitySet.delete(flower);
|
||||
video.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function updateRecordPlayerProximity() {
|
||||
proximitySet.forEach((el) => el.firstElementChild.pause?.());
|
||||
proximitySet.clear();
|
||||
|
||||
for (const flower of flowers) {
|
||||
updateFlowerProximity(flower);
|
||||
}
|
||||
}
|
||||
|
||||
function handleProximity(e) {
|
||||
if (e.target === recordPlayerGeometry) {
|
||||
updateRecordPlayerProximity();
|
||||
} else {
|
||||
updateFlowerProximity(e.target);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('move', handleProximity);
|
||||
document.addEventListener('resize', handleProximity);
|
||||
|
||||
document.addEventListener('playing', (e) => {
|
||||
proximitySet.forEach((el) => el.firstElementChild.play?.());
|
||||
});
|
||||
|
||||
audio.addEventListener('pause', () => {
|
||||
flowers.forEach((el) => {
|
||||
el.pause();
|
||||
el.currentTime = 0;
|
||||
document.addEventListener('stopped', (e) => {
|
||||
proximitySet.forEach((el) => {
|
||||
el.firstElementChild.pause?.();
|
||||
el.firstElementChild.currentTime = 0;
|
||||
});
|
||||
});
|
||||
|
||||
function updateVolume() {
|
||||
proximityDistance = recordPlayer.volume * 500;
|
||||
updateRecordPlayerProximity();
|
||||
}
|
||||
|
||||
updateVolume();
|
||||
|
||||
document.addEventListener('volume', updateVolume);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ styles.replaceSync(`
|
|||
top: 25px;
|
||||
right: 95px;
|
||||
transform-origin: top;
|
||||
|
||||
--move-time: 3s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
.control {
|
||||
|
|
@ -150,20 +154,17 @@ styles.replaceSync(`
|
|||
}
|
||||
|
||||
:host(:state(playing)) .tone-arm {
|
||||
--move-time: 3s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: linear;
|
||||
animation:
|
||||
position-arm var(--move-time),
|
||||
ready-arm var(--move-time),
|
||||
move-arm var(--duration) var(--move-time),
|
||||
reset-arm var(--move-time) calc(var(--duration) + var(--move-time));
|
||||
}
|
||||
}
|
||||
|
||||
:host(:state(playing)) .record {
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes position-arm {
|
||||
@keyframes ready-arm {
|
||||
20% {
|
||||
transform: rotateX(20deg);
|
||||
}
|
||||
|
|
@ -184,14 +185,18 @@ styles.replaceSync(`
|
|||
}
|
||||
|
||||
to {
|
||||
rotate: 43deg;
|
||||
rotate: 42deg;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes reset-arm {
|
||||
0% {
|
||||
rotate: 42deg;
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotateX(20deg);
|
||||
rotate: 43deg;
|
||||
rotate: 42deg;
|
||||
}
|
||||
|
||||
80% {
|
||||
|
|
@ -225,6 +230,8 @@ export class RecordPlayer extends HTMLElement {
|
|||
super();
|
||||
|
||||
this.addEventListener('click', this);
|
||||
|
||||
this.#audio.volume = 0.5;
|
||||
this.#audio.addEventListener('ended', this);
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
|
|
@ -239,7 +246,7 @@ export class RecordPlayer extends HTMLElement {
|
|||
<div class="control"></div>
|
||||
</div>
|
||||
<button class="btn"></button>
|
||||
<input type="range" class="slider" min="0" max="1" step="0.05" value="0.75">
|
||||
<input type="range" class="slider" min="0" max="1" step="0.05" value="0.5">
|
||||
</div>
|
||||
<slot></slot>`;
|
||||
|
||||
|
|
@ -251,6 +258,10 @@ export class RecordPlayer extends HTMLElement {
|
|||
return this.#audio.paused;
|
||||
}
|
||||
|
||||
get volume() {
|
||||
return this.#audio.volume;
|
||||
}
|
||||
|
||||
#playTimeout: number = -1;
|
||||
|
||||
play() {
|
||||
|
|
@ -259,7 +270,10 @@ export class RecordPlayer extends HTMLElement {
|
|||
this.#audio.volume = this.#volumeInput.valueAsNumber;
|
||||
this.style.setProperty('--duration', `${this.#audio.duration}s`);
|
||||
this.#internals.states.add('playing');
|
||||
this.#playTimeout = window.setTimeout(() => this.#audio.play(), 3000);
|
||||
this.#playTimeout = window.setTimeout(() => {
|
||||
this.#audio.play();
|
||||
this.dispatchEvent(new Event('playing', { bubbles: true }));
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
|
@ -269,6 +283,7 @@ export class RecordPlayer extends HTMLElement {
|
|||
this.#internals.states.delete('playing');
|
||||
this.#audio.pause();
|
||||
this.#audio.currentTime = 0;
|
||||
this.dispatchEvent(new Event('stopped', { bubbles: true }));
|
||||
}
|
||||
|
||||
handleEvent(event: Event) {
|
||||
|
|
@ -283,7 +298,9 @@ export class RecordPlayer extends HTMLElement {
|
|||
}
|
||||
|
||||
case 'input': {
|
||||
event.stopPropagation();
|
||||
this.#audio.volume = this.#volumeInput.valueAsNumber;
|
||||
this.dispatchEvent(new Event('volume', { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue