Analyser placement tweaks

This commit is contained in:
2026-03-12 11:52:29 +00:00
parent c315927cb6
commit bde579e515
6 changed files with 96 additions and 45 deletions
+21 -20
View File
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, effect, ElementRef, inject, OnInit, ViewChild, viewChild} from '@angular/core'; import {AfterViewInit, Component, effect, ElementRef, inject, ViewChild} from '@angular/core';
import {AudioService} from '../../services/audio.service'; import {AudioService} from '../../services/audio.service';
@Component({ @Component({
@@ -6,6 +6,10 @@ import {AudioService} from '../../services/audio.service';
templateUrl: 'analyser.html', templateUrl: 'analyser.html',
}) })
export class Analyser implements AfterViewInit { export class Analyser implements AfterViewInit {
public readonly height = 150;
public readonly bottomOffset = 50;
public readonly offsetBuffer = 20;
public color = "rgb(80,0,80)"
protected audio: AudioService = inject(AudioService); protected audio: AudioService = inject(AudioService);
@ViewChild('analyserCanvas') canvasRef!: ElementRef<HTMLCanvasElement>; @ViewChild('analyserCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
protected canvas!: HTMLCanvasElement; protected canvas!: HTMLCanvasElement;
@@ -20,6 +24,17 @@ export class Analyser implements AfterViewInit {
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.canvas = this.canvasRef.nativeElement as HTMLCanvasElement; this.canvas = this.canvasRef.nativeElement as HTMLCanvasElement;
this.canvas.style.height = `${this.height}px`;
this.canvas.style.top = `-${this.height}px`;
document.onmousemove = (e: MouseEvent) => {
const cursorDistancefromBottom = document.documentElement.clientHeight - (e.clientY || 0);
let offset = cursorDistancefromBottom - (this.bottomOffset + this.offsetBuffer);
if (cursorDistancefromBottom < this.bottomOffset || cursorDistancefromBottom > (this.bottomOffset + this.height + this.offsetBuffer)) {
offset = this.height;
}
this.canvas.style.height = `${offset}px`;
this.canvas.style.top = `-${offset}px`;
}
} }
draw() { draw() {
@@ -29,34 +44,20 @@ export class Analyser implements AfterViewInit {
const data = this.audio.analyser.analyserData; const data = this.audio.analyser.analyserData;
if (!ctx) return; if (!ctx) return;
ctx.fillStyle = "rgb(200 200 200)"; ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 0;
ctx.fillStyle = this.color;
ctx.lineWidth = 2;
ctx.strokeStyle = "rgb(0 0 0)";
ctx.beginPath();
const sliceWidth = canvas.width / data.length;
const sliceWidth = Math.ceil(canvas.width / data.length);
let x = 0; let x = 0;
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const v = data[i] / 128.0; const v = data[i] / 128.0;
const y = (v * canvas.height) / 2; const y = (v * canvas.height) / 2;
if (i === 0) { ctx.fillRect(x, canvas.height, sliceWidth, -y);
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
x += sliceWidth; x += sliceWidth;
} }
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
requestAnimationFrame(this.draw.bind(this)); requestAnimationFrame(this.draw.bind(this));
} }
} }
+1 -1
View File
@@ -1 +1 @@
<canvas #analyserCanvas width="1080" height="300"></canvas> <canvas #analyserCanvas width="1080" [height]="height" style="width: 100vw; position: absolute;"></canvas>
+49 -3
View File
@@ -1,4 +1,10 @@
Home <div style="position: fixed; height: 50px; background: rgb(80,0,80); bottom: 0; width: 100vw">
<analyser></analyser>
</div>
Home<br/>
Home<br/>
Home<br/>
<button (click)="play($event)">Play</button> <button (click)="play($event)">Play</button>
<button (click)="stop($event)">Stop</button> <button (click)="stop($event)">Stop</button>
@@ -13,10 +19,50 @@ Home
@if ($index === audio.index()) { @if ($index === audio.index()) {
* *
} }
{{ item.track.file.filename }} <a (click)="setIndex($index)">{{ item.track.file.filename }}</a>
</div> </div>
} }
<div>{{ audio.current()?.currentTime()}} / {{audio.current()?.duration()}} -> {{ audio.progress() * 100 }} %</div> <div>{{ audio.current()?.currentTime()}} / {{audio.current()?.duration()}} -> {{ audio.progress() * 100 }} %</div>
<input #range width="100%" type="range" value="0" min="0" max="100" step="0.1" (change)="seek($event)"> <input #range width="100%" type="range" value="0" min="0" max="100" step="0.1" (change)="seek($event)">
<analyser></analyser> Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
Home<br/>
<hr>
+18 -16
View File
@@ -26,28 +26,32 @@ export class HomePage implements AfterViewInit{
this.input = this.rangeRef.nativeElement as HTMLInputElement; this.input = this.rangeRef.nativeElement as HTMLInputElement;
} }
seek(event: Event) { async seek(event: Event) {
this.audio.seekPercentage(Number.parseFloat(this.input.value)); await this.audio.seekPercentage(Number.parseFloat(this.input.value));
} }
play(event: Event): void { async play(event: Event): Promise<void> {
this.audio.play(); await this.audio.play();
} }
stop(event: Event): void { async stop(event: Event): Promise<void> {
this.audio.stop(); await this.audio.stop();
} }
next(event: Event): void { async next(event: Event): Promise<void> {
this.audio.next(); await this.audio.next();
} }
prev(event: Event): void { async prev(event: Event): Promise<void> {
this.audio.prev(); await this.audio.prev();
} }
pause(event: Event): void { async pause(event: Event): Promise<void> {
this.audio.pause(); await this.audio.pause();
}
async setIndex(index: number): Promise<void> {
await this.audio.setIndex(index);
} }
add(event: Event): void { add(event: Event): void {
@@ -60,10 +64,8 @@ export class HomePage implements AfterViewInit{
)); ));
} }
remove(event: Event): void { async remove(event: Event): Promise<void> {
const item = this.audio.current(); const item = this.audio.current();
if (item) { if (item) await this.audio.removeItem(item);
this.audio.removeItem(item);
}
} }
} }
+6 -4
View File
@@ -132,18 +132,20 @@ export class AudioService {
} }
/** /**
* Start playing the track at position "index" in the playlist. * Update current index of the playlist.
* Start playing the selected track if we were already playing a track.
* Deliberately take no action if the track referenced is already the current track. * Deliberately take no action if the track referenced is already the current track.
* @param index * @param index
*/ */
async playIndex(index: number): Promise<void> { async setIndex(index: number): Promise<void> {
const current = this.current();
const target = this.playlist.getIndex(index); const target = this.playlist.getIndex(index);
const current = this.current();
const wasPlaying = current?.isPlaying();
if (!target || current === target) return; if (!target || current === target) return;
await current?.stop(); await current?.stop();
this.playlist.setIndex(index); this.playlist.setIndex(index);
await target.play(); if (wasPlaying) await target.play();
} }
/** /**
+1 -1
View File
@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body style="margin: 0 0 60px 0">
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>