annotate src/app/app.component.ts @ 357:b529a08ddff1

Explicitly state the change detection strategy, despite the fact it will already be OnPush here because its parent is.
author Lucas Thompson <dev@lucas.im>
date Fri, 26 May 2017 18:30:58 +0100
parents 02e7be2daf31
children bcb0a172eca3
rev   line source
dev@193 1 import {Component, OnDestroy} from '@angular/core';
dev@193 2 import {
dev@193 3 AudioPlayerService,
dev@193 4 AudioResourceError, AudioResource
dev@236 5 } from './services/audio-player/audio-player.service';
dev@236 6 import {FeatureExtractionService} from './services/feature-extraction/feature-extraction.service';
dev@236 7 import {ExtractorOutputInfo} from './feature-extraction-menu/feature-extraction-menu.component';
dev@89 8 import {DomSanitizer} from '@angular/platform-browser';
dev@89 9 import {MdIconRegistry} from '@angular/material';
dev@236 10 import {Subscription} from 'rxjs/Subscription';
dev@350 11 import {
dev@353 12 AnalysisItem,
dev@353 13 isRootAudioItem,
dev@350 14 Item, PendingAnalysisItem, PendingRootAudioItem, RootAudioItem
dev@350 15 } from './analysis-item/analysis-item.component';
dev@347 16 import {OnSeekHandler} from './playhead/PlayHeadHelpers';
angular-cli@0 17
dev@235 18 class PersistentStack<T> {
dev@235 19 private stack: T[];
dev@235 20 private history: T[][];
dev@235 21
dev@235 22 constructor() {
dev@235 23 this.stack = [];
dev@235 24 this.history = [];
dev@235 25 }
dev@235 26
dev@235 27 shift(): T {
dev@235 28 this.history.push([...this.stack]);
dev@235 29 const item = this.stack[0];
dev@235 30 this.stack = this.stack.slice(1);
dev@235 31 return item;
dev@235 32 }
dev@235 33
dev@236 34 unshift(item: T): number {
dev@235 35 this.history.push([...this.stack]);
dev@235 36 this.stack = [item, ...this.stack];
dev@235 37 return this.stack.length;
dev@235 38 }
dev@235 39
dev@235 40 findIndex(predicate: (value: T,
dev@235 41 index: number,
dev@235 42 array: T[]) => boolean): number {
dev@235 43 return this.stack.findIndex(predicate);
dev@235 44 }
dev@235 45
dev@235 46 filter(predicate: (value: T, index: number, array: T[]) => boolean): T[] {
dev@235 47 return this.stack.filter(predicate);
dev@235 48 }
dev@235 49
dev@235 50 get(index: number): T {
dev@235 51 return this.stack[index];
dev@235 52 }
dev@235 53
dev@235 54 set(index: number, value: T) {
dev@235 55 this.history.push([...this.stack]);
dev@235 56 this.stack = [
dev@235 57 ...this.stack.slice(0, index),
dev@235 58 value,
dev@235 59 ...this.stack.slice(index + 1)
dev@235 60 ];
dev@235 61 }
dev@235 62
dev@235 63 toIterable(): Iterable<T> {
dev@235 64 return this.stack;
dev@235 65 }
dev@235 66 }
dev@235 67
angular-cli@0 68 @Component({
dev@236 69 selector: 'ugly-root',
angular-cli@0 70 templateUrl: './app.component.html',
angular-cli@0 71 styleUrls: ['./app.component.css']
angular-cli@0 72 })
dev@193 73 export class AppComponent implements OnDestroy {
dev@31 74 audioBuffer: AudioBuffer; // TODO consider revising
dev@49 75 canExtract: boolean;
dev@193 76 private onAudioDataSubscription: Subscription;
dev@226 77 private onProgressUpdated: Subscription;
dev@350 78 private analyses: PersistentStack<Item>; // TODO some immutable state container describing entire session
dev@203 79 private nRecordings: number; // TODO user control for naming a recording
dev@206 80 private countingId: number; // TODO improve uniquely identifying items
dev@350 81 private rootAudioItem: RootAudioItem;
dev@347 82 private onSeek: OnSeekHandler;
dev@1 83
dev@47 84 constructor(private audioService: AudioPlayerService,
dev@228 85 private featureService: FeatureExtractionService,
dev@89 86 private iconRegistry: MdIconRegistry,
dev@89 87 private sanitizer: DomSanitizer) {
dev@235 88 this.analyses = new PersistentStack<AnalysisItem>();
dev@49 89 this.canExtract = false;
dev@203 90 this.nRecordings = 0;
dev@226 91 this.countingId = 0;
dev@347 92 this.onSeek = (time) => this.audioService.seekTo(time);
dev@353 93 this.rootAudioItem = {} as any; // TODO eugh
dev@206 94
dev@89 95 iconRegistry.addSvgIcon(
dev@89 96 'duck',
dev@89 97 sanitizer.bypassSecurityTrustResourceUrl('assets/duck.svg')
dev@89 98 );
dev@193 99
dev@193 100 this.onAudioDataSubscription = this.audioService.audioLoaded$.subscribe(
dev@193 101 resource => {
dev@193 102 const wasError = (resource as AudioResourceError).message != null;
dev@193 103 if (wasError) {
dev@203 104 this.analyses.shift();
dev@193 105 this.canExtract = false;
dev@193 106 } else {
dev@193 107 this.audioBuffer = (resource as AudioResource).samples;
dev@350 108 this.rootAudioItem.audioData = this.audioBuffer;
dev@193 109 if (this.audioBuffer) {
dev@193 110 this.canExtract = true;
dev@347 111 const currentRootIndex = this.analyses.findIndex(val => {
dev@350 112 return isRootAudioItem(val) && val.uri === this.rootAudioItem.uri;
dev@347 113 });
dev@347 114 if (currentRootIndex !== -1) {
dev@347 115 this.analyses.set(
dev@347 116 currentRootIndex,
dev@347 117 Object.assign(
dev@347 118 {},
dev@347 119 this.analyses.get(currentRootIndex),
dev@347 120 {audioData: this.audioBuffer}
dev@347 121 )
dev@347 122 );
dev@347 123 }
dev@193 124 }
dev@193 125 }
dev@193 126 }
dev@193 127 );
dev@228 128 this.onProgressUpdated = this.featureService.progressUpdated$.subscribe(
dev@226 129 progress => {
dev@226 130 const index = this.analyses.findIndex(val => val.id === progress.id);
dev@236 131 if (index === -1) {
dev@236 132 return;
dev@236 133 }
dev@235 134
dev@235 135 this.analyses.set(
dev@235 136 index,
dev@235 137 Object.assign(
dev@235 138 {},
dev@235 139 this.analyses.get(index),
dev@235 140 {progress: progress.value}
dev@235 141 )
dev@235 142 );
dev@226 143 }
dev@226 144 );
dev@48 145 }
dev@16 146
dev@134 147 onFileOpened(file: File | Blob) {
dev@49 148 this.canExtract = false;
dev@203 149 const url = this.audioService.loadAudio(file);
dev@203 150 // TODO is it safe to assume it is a recording?
dev@203 151 const title = (file instanceof File) ?
dev@203 152 (file as File).name : `Recording ${this.nRecordings++}`;
dev@203 153
dev@203 154 if (this.analyses.filter(item => item.title === title).length > 0) {
dev@203 155 // TODO this reveals how brittle the current name / uri based id is
dev@203 156 // need something more robust, and also need to notify the user
dev@203 157 // in a suitable way in the actual event of a duplicate file
dev@203 158 console.warn('There is already a notebook based on this audio file.');
dev@203 159 return;
dev@203 160 }
dev@203 161
dev@350 162 const pending = {
dev@350 163 uri: url,
dev@203 164 hasSharedTimeline: true,
dev@203 165 title: title,
dev@206 166 description: new Date().toLocaleString(),
dev@226 167 id: `${++this.countingId}`
dev@350 168 } as PendingRootAudioItem;
dev@350 169 this.rootAudioItem = pending as RootAudioItem; // TODO this is silly
dev@350 170
dev@350 171 // TODO re-ordering of items for display
dev@350 172 // , one alternative is a Angular Pipe / Filter for use in the Template
dev@350 173 this.analyses.unshift(pending);
dev@16 174 }
dev@47 175
dev@48 176 extractFeatures(outputInfo: ExtractorOutputInfo): void {
dev@236 177 if (!this.canExtract || !outputInfo) {
dev@236 178 return;
dev@236 179 }
dev@236 180
dev@49 181 this.canExtract = false;
dev@203 182
dev@350 183 const placeholderCard: PendingAnalysisItem = {
dev@350 184 parent: this.rootAudioItem,
dev@203 185 hasSharedTimeline: true,
dev@203 186 extractorKey: outputInfo.combinedKey,
dev@203 187 title: outputInfo.name,
dev@206 188 description: outputInfo.outputId,
dev@227 189 id: `${++this.countingId}`,
dev@227 190 progress: 0
dev@350 191 };
dev@350 192 this.analyses.unshift(placeholderCard);
dev@203 193
dev@228 194 this.featureService.extract(`${this.countingId}`, {
dev@47 195 audioData: [...Array(this.audioBuffer.numberOfChannels).keys()]
dev@47 196 .map(i => this.audioBuffer.getChannelData(i)),
dev@47 197 audioFormat: {
dev@47 198 sampleRate: this.audioBuffer.sampleRate,
dev@226 199 channelCount: this.audioBuffer.numberOfChannels,
dev@226 200 length: this.audioBuffer.length
dev@47 201 },
dev@47 202 key: outputInfo.extractorKey,
dev@47 203 outputId: outputInfo.outputId
dev@50 204 }).then(() => {
dev@49 205 this.canExtract = true;
dev@115 206 }).catch(err => {
dev@115 207 this.canExtract = true;
dev@226 208 this.analyses.shift();
dev@226 209 console.error(`Error whilst extracting: ${err}`);
dev@115 210 });
dev@47 211 }
dev@193 212
dev@193 213 ngOnDestroy(): void {
dev@193 214 this.onAudioDataSubscription.unsubscribe();
dev@226 215 this.onProgressUpdated.unsubscribe();
dev@193 216 }
angular-cli@0 217 }