annotate src/app/app.component.ts @ 486:c9f12a9c1d5c

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