MOBILE-2314 fileuploader: Create audio-histogram
parent
ba723dd899
commit
bb64922a14
|
@ -0,0 +1 @@
|
||||||
|
<canvas #canvas></canvas>
|
|
@ -0,0 +1,10 @@
|
||||||
|
:host {
|
||||||
|
--background-color: var(--ion-background-color, #fff);
|
||||||
|
--bars-color: var(--ion-text-color, #000);
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'core-audio-histogram',
|
||||||
|
templateUrl: 'audio-histogram.html',
|
||||||
|
styleUrls: ['audio-histogram.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class CoreFileUploaderAudioHistogramComponent implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
private static readonly BARS_WIDTH = 2;
|
||||||
|
private static readonly BARS_MIN_HEIGHT = 4;
|
||||||
|
private static readonly BARS_GUTTER = 4;
|
||||||
|
|
||||||
|
@Input() analyser!: AnalyserNode;
|
||||||
|
@Input() paused?: boolean;
|
||||||
|
@ViewChild('canvas') canvasRef?: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
|
private element: HTMLElement;
|
||||||
|
private canvas?: HTMLCanvasElement;
|
||||||
|
private context?: CanvasRenderingContext2D | null;
|
||||||
|
private buffer?: Uint8Array;
|
||||||
|
private destroyed = false;
|
||||||
|
|
||||||
|
constructor({ nativeElement }: ElementRef<HTMLElement>) {
|
||||||
|
this.element = nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.canvas = this.canvasRef?.nativeElement;
|
||||||
|
this.context = this.canvas?.getContext('2d');
|
||||||
|
this.buffer = new Uint8Array(this.analyser.fftSize);
|
||||||
|
|
||||||
|
if (this.context && this.canvas) {
|
||||||
|
const styles = getComputedStyle(this.element);
|
||||||
|
|
||||||
|
this.canvas.width = this.canvas.clientWidth;
|
||||||
|
this.canvas.height = this.canvas.clientHeight;
|
||||||
|
this.context.fillStyle = styles.getPropertyValue('--background-color');
|
||||||
|
this.context.lineCap = 'round';
|
||||||
|
this.context.lineWidth = CoreFileUploaderAudioHistogramComponent.BARS_WIDTH;
|
||||||
|
this.context.strokeStyle = styles.getPropertyValue('--bars-color');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroyed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw histogram.
|
||||||
|
*/
|
||||||
|
private draw(): void {
|
||||||
|
if (this.destroyed || !this.canvas || !this.context || !this.buffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = this.canvas.width;
|
||||||
|
const height = this.canvas.height;
|
||||||
|
const barsWidth = CoreFileUploaderAudioHistogramComponent.BARS_WIDTH;
|
||||||
|
const barsGutter = CoreFileUploaderAudioHistogramComponent.BARS_GUTTER;
|
||||||
|
const chunkLength = Math.floor(this.buffer.length / ((width - barsWidth - 1) / (barsWidth + barsGutter)));
|
||||||
|
const barsCount = Math.floor(this.buffer.length / chunkLength);
|
||||||
|
|
||||||
|
// Reset canvas.
|
||||||
|
this.context.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Draw bars.
|
||||||
|
const startX = Math.floor((width - (barsWidth + barsGutter)*barsCount - barsWidth - 1)/2);
|
||||||
|
|
||||||
|
this.context.beginPath();
|
||||||
|
this.paused ? this.drawPausedBars(startX) : this.drawActiveBars(startX);
|
||||||
|
this.context.stroke();
|
||||||
|
|
||||||
|
// Schedule next frame.
|
||||||
|
requestAnimationFrame(() => this.draw());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws bars on the histogram when it is active.
|
||||||
|
*
|
||||||
|
* @param x Starting x position.
|
||||||
|
*/
|
||||||
|
private drawActiveBars(x: number): void {
|
||||||
|
if (!this.canvas || !this.context || !this.buffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bufferX = 0;
|
||||||
|
const width = this.canvas.width;
|
||||||
|
const halfHeight = this.canvas.height / 2;
|
||||||
|
const halfMinHeight = CoreFileUploaderAudioHistogramComponent.BARS_MIN_HEIGHT / 2;
|
||||||
|
const barsWidth = CoreFileUploaderAudioHistogramComponent.BARS_WIDTH;
|
||||||
|
const barsGutter = CoreFileUploaderAudioHistogramComponent.BARS_GUTTER;
|
||||||
|
const bufferLength = this.buffer.length;
|
||||||
|
const barsBufferWidth = Math.floor(bufferLength / ((width - barsWidth - 1) / (barsWidth + barsGutter)));
|
||||||
|
|
||||||
|
this.analyser.getByteTimeDomainData(this.buffer);
|
||||||
|
|
||||||
|
while (bufferX < bufferLength) {
|
||||||
|
let maxLevel = halfMinHeight;
|
||||||
|
|
||||||
|
do {
|
||||||
|
maxLevel = Math.max(maxLevel, halfHeight * (1 - (this.buffer[bufferX] / 128)));
|
||||||
|
bufferX++;
|
||||||
|
} while (bufferX % barsBufferWidth !== 0 && bufferX < bufferLength);
|
||||||
|
|
||||||
|
this.context.moveTo(x, halfHeight - maxLevel);
|
||||||
|
this.context.lineTo(x, halfHeight + maxLevel);
|
||||||
|
|
||||||
|
x += barsWidth + barsGutter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws bars on the histogram when it is paused.
|
||||||
|
*
|
||||||
|
* @param x Starting x position.
|
||||||
|
*/
|
||||||
|
private drawPausedBars(x: number): void {
|
||||||
|
if (!this.canvas || !this.context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = this.canvas.width;
|
||||||
|
const halfHeight = this.canvas.height / 2;
|
||||||
|
const halfMinHeight = CoreFileUploaderAudioHistogramComponent.BARS_MIN_HEIGHT / 2;
|
||||||
|
const xStep = CoreFileUploaderAudioHistogramComponent.BARS_WIDTH + CoreFileUploaderAudioHistogramComponent.BARS_GUTTER;
|
||||||
|
|
||||||
|
while (x < width) {
|
||||||
|
this.context.moveTo(x, halfHeight - halfMinHeight);
|
||||||
|
this.context.lineTo(x, halfHeight + halfMinHeight);
|
||||||
|
|
||||||
|
x += xStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue