annotate src/app/app.component.ts @ 509:041468f553e1 tip master

Merge pull request #57 from LucasThompson/fix/session-stack-max-call-stack Fix accidental recursion in PersistentStack
author Lucas Thompson <LucasThompson@users.noreply.github.com>
date Mon, 27 Nov 2017 11:04:30 +0000
parents cf4a17efb5d4
children
rev   line source
dev@456 1 import {Component, Inject, OnDestroy} from '@angular/core';
dev@193 2 import {
dev@193 3 AudioPlayerService,
dev@456 4 AudioResourceError,
dev@494 5 AudioResource,
dev@494 6 AudioLoadResponse
dev@236 7 } from './services/audio-player/audio-player.service';
dev@460 8 import {
dev@460 9 ExtractionResult,
dev@460 10 FeatureExtractionService
dev@460 11 } from './services/feature-extraction/feature-extraction.service';
dev@236 12 import {ExtractorOutputInfo} from './feature-extraction-menu/feature-extraction-menu.component';
dev@89 13 import {DomSanitizer} from '@angular/platform-browser';
dev@505 14 import {MatIconRegistry} from '@angular/material';
dev@236 15 import {Subscription} from 'rxjs/Subscription';
dev@350 16 import {
dev@353 17 AnalysisItem,
dev@460 18 isPendingAnalysisItem,
dev@460 19 isPendingRootAudioItem,
dev@460 20 isLoadedRootAudioItem,
dev@456 21 Item,
dev@460 22 RootAudioItem,
dev@464 23 getRootAudioItem
dev@460 24 } from './analysis-item/AnalysisItem';
dev@347 25 import {OnSeekHandler} from './playhead/PlayHeadHelpers';
dev@497 26 import {UrlResourceLifetimeManager} from './services/File';
dev@460 27 import {createExtractionRequest} from './analysis-item/AnalysisItem';
dev@460 28 import {PersistentStack} from './Session';
dev@494 29 import {NotificationService} from './services/notifications/notifications.service';
dev@235 30
angular-cli@0 31 @Component({
dev@236 32 selector: 'ugly-root',
angular-cli@0 33 templateUrl: './app.component.html',
angular-cli@0 34 styleUrls: ['./app.component.css']
angular-cli@0 35 })
dev@193 36 export class AppComponent implements OnDestroy {
dev@49 37 canExtract: boolean;
dev@193 38 private onAudioDataSubscription: Subscription;
dev@226 39 private onProgressUpdated: Subscription;
dev@350 40 private analyses: PersistentStack<Item>; // TODO some immutable state container describing entire session
dev@203 41 private nRecordings: number; // TODO user control for naming a recording
dev@206 42 private countingId: number; // TODO improve uniquely identifying items
dev@347 43 private onSeek: OnSeekHandler;
dev@1 44
dev@47 45 constructor(private audioService: AudioPlayerService,
dev@228 46 private featureService: FeatureExtractionService,
dev@505 47 private iconRegistry: MatIconRegistry,
dev@456 48 private sanitizer: DomSanitizer,
dev@456 49 @Inject(
dev@456 50 'UrlResourceLifetimeManager'
dev@494 51 ) private resourceManager: UrlResourceLifetimeManager,
dev@494 52 private notifier: NotificationService) {
dev@235 53 this.analyses = new PersistentStack<AnalysisItem>();
dev@49 54 this.canExtract = false;
dev@203 55 this.nRecordings = 0;
dev@226 56 this.countingId = 0;
dev@347 57 this.onSeek = (time) => this.audioService.seekTo(time);
dev@206 58
dev@89 59 iconRegistry.addSvgIcon(
dev@89 60 'duck',
dev@89 61 sanitizer.bypassSecurityTrustResourceUrl('assets/duck.svg')
dev@89 62 );
dev@193 63
dev@193 64 this.onAudioDataSubscription = this.audioService.audioLoaded$.subscribe(
dev@193 65 resource => {
dev@486 66 const findCurrentAudio =
dev@486 67 val => isPendingRootAudioItem(val) && val.uri === getRootAudioItem(
dev@486 68 this.analyses.get(0)
dev@486 69 ).uri;
dev@494 70 const wasError = (res: AudioLoadResponse):
dev@494 71 res is AudioResourceError => (res as any).message != null;
dev@494 72 if (wasError(resource)) {
dev@494 73 this.notifier.displayError(resource.message);
dev@486 74 this.analyses.findIndexAndUse(
dev@486 75 findCurrentAudio,
dev@486 76 index => this.analyses.remove(index)
dev@486 77 );
dev@193 78 this.canExtract = false;
dev@193 79 } else {
dev@464 80 const audioData = (resource as AudioResource).samples;
dev@464 81 if (audioData) {
dev@193 82 this.canExtract = true;
dev@486 83 this.analyses.findIndexAndUse(
dev@486 84 findCurrentAudio,
dev@486 85 currentRootIndex => this.analyses.set(
dev@347 86 currentRootIndex,
dev@347 87 Object.assign(
dev@347 88 {},
dev@347 89 this.analyses.get(currentRootIndex),
dev@464 90 {audioData}
dev@347 91 )
dev@486 92 ));
dev@193 93 }
dev@193 94 }
dev@193 95 }
dev@193 96 );
dev@228 97 this.onProgressUpdated = this.featureService.progressUpdated$.subscribe(
dev@226 98 progress => {
dev@486 99 this.analyses.findIndexAndUse(
dev@486 100 val => val.id === progress.id,
dev@486 101 index => this.analyses.setMutating(
dev@486 102 index,
dev@486 103 Object.assign(
dev@486 104 {},
dev@486 105 this.analyses.get(index),
dev@486 106 {progress: progress.value}
dev@486 107 )
dev@235 108 )
dev@235 109 );
dev@226 110 }
dev@226 111 );
dev@48 112 }
dev@16 113
dev@456 114 onFileOpened(file: File | Blob, createExportableItem = false) {
dev@49 115 this.canExtract = false;
dev@203 116 const url = this.audioService.loadAudio(file);
dev@203 117 // TODO is it safe to assume it is a recording?
dev@203 118 const title = (file instanceof File) ?
dev@203 119 (file as File).name : `Recording ${this.nRecordings++}`;
dev@203 120
dev@203 121 if (this.analyses.filter(item => item.title === title).length > 0) {
dev@203 122 // TODO this reveals how brittle the current name / uri based id is
dev@203 123 // need something more robust, and also need to notify the user
dev@203 124 // in a suitable way in the actual event of a duplicate file
dev@203 125 console.warn('There is already a notebook based on this audio file.');
dev@203 126 return;
dev@203 127 }
dev@203 128
dev@350 129 const pending = {
dev@350 130 uri: url,
dev@203 131 hasSharedTimeline: true,
dev@203 132 title: title,
dev@206 133 description: new Date().toLocaleString(),
dev@453 134 id: `${++this.countingId}`,
dev@456 135 mimeType: file.type,
dev@456 136 isExportable: createExportableItem
dev@460 137 } as RootAudioItem;
dev@350 138
dev@350 139 // TODO re-ordering of items for display
dev@350 140 // , one alternative is a Angular Pipe / Filter for use in the Template
dev@466 141 this.analyses.unshiftMutating(pending);
dev@16 142 }
dev@47 143
dev@460 144 extractFeatures(outputInfo: ExtractorOutputInfo): string {
dev@236 145 if (!this.canExtract || !outputInfo) {
dev@236 146 return;
dev@236 147 }
dev@236 148
dev@49 149 this.canExtract = false;
dev@203 150
dev@464 151 const rootAudio = getRootAudioItem(this.analyses.get(0));
dev@464 152
dev@464 153 if (isLoadedRootAudioItem(rootAudio)) {
dev@464 154 const placeholderCard: AnalysisItem = {
dev@464 155 parent: rootAudio,
dev@464 156 hasSharedTimeline: true,
dev@464 157 extractorKey: outputInfo.extractorKey,
dev@464 158 outputId: outputInfo.outputId,
dev@464 159 title: outputInfo.name,
dev@464 160 description: outputInfo.outputId,
dev@464 161 id: `${++this.countingId}`,
dev@464 162 progress: 0
dev@464 163 };
dev@466 164 this.analyses.unshiftMutating(placeholderCard);
dev@464 165 this.sendExtractionRequest(placeholderCard);
dev@464 166 return placeholderCard.id;
dev@464 167 }
dev@464 168 throw new Error('Cannot extract. No audio loaded');
dev@47 169 }
dev@193 170
dev@456 171 removeItem(item: Item): void {
dev@456 172 const indicesToRemove: number[] = this.analyses.reduce(
dev@456 173 (toRemove, current, index) => {
dev@456 174 if (isPendingAnalysisItem(current) && current.parent.id === item.id) {
dev@456 175 toRemove.push(index);
dev@456 176 } else if (item.id === current.id) {
dev@456 177 toRemove.push(index);
dev@456 178 }
dev@456 179 return toRemove;
dev@456 180 }, []);
dev@459 181 this.analyses.remove(...indicesToRemove);
dev@456 182 }
dev@456 183
dev@193 184 ngOnDestroy(): void {
dev@193 185 this.onAudioDataSubscription.unsubscribe();
dev@226 186 this.onProgressUpdated.unsubscribe();
dev@193 187 }
dev@460 188
dev@460 189 private sendExtractionRequest(analysis: AnalysisItem): Promise<void> {
dev@460 190 const findAndUpdateItem = (result: ExtractionResult): void => {
dev@460 191 // TODO subscribe to the extraction service instead
dev@486 192 this.analyses.findIndexAndUse(
dev@486 193 val => val.id === result.id,
dev@486 194 (index) => this.analyses.set(
dev@486 195 index,
dev@460 196 Object.assign(
dev@460 197 {},
dev@486 198 this.analyses.get(index),
dev@460 199 result.result,
dev@460 200 result.unit ? {unit: result.unit} : {}
dev@460 201 )
dev@486 202 )
dev@486 203 );
dev@486 204 this.canExtract = true;
dev@460 205 };
dev@460 206 return this.featureService.extract(
dev@460 207 analysis.id,
dev@460 208 createExtractionRequest(analysis))
dev@460 209 .then(findAndUpdateItem)
dev@460 210 .catch(err => {
dev@460 211 this.canExtract = true;
dev@486 212 this.analyses.findIndexAndUse(
dev@486 213 val => val.id === analysis.id,
dev@486 214 index => this.analyses.remove(index)
dev@486 215 );
dev@494 216 this.notifier.displayError(`Error whilst extracting: ${err}`);
dev@460 217 });
dev@460 218 }
angular-cli@0 219 }