annotate src/app/app.component.ts @ 477:e3cbf4c53e5e

Rename type and make it an abstract class to stop angular-cli complaining (I still don't understand this).
author Lucas Thompson <dev@lucas.im>
date Fri, 30 Jun 2017 16:25:58 +0100
parents 8820a133bcf5
children c9f12a9c1d5c
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@193 63 const wasError = (resource as AudioResourceError).message != null;
dev@193 64 if (wasError) {
dev@203 65 this.analyses.shift();
dev@193 66 this.canExtract = false;
dev@193 67 } else {
dev@464 68 const audioData = (resource as AudioResource).samples;
dev@464 69 if (audioData) {
dev@464 70 const rootAudio = getRootAudioItem(this.analyses.get(0));
dev@193 71 this.canExtract = true;
dev@347 72 const currentRootIndex = this.analyses.findIndex(val => {
dev@464 73 return isPendingRootAudioItem(val) && val.uri === rootAudio.uri;
dev@347 74 });
dev@347 75 if (currentRootIndex !== -1) {
dev@347 76 this.analyses.set(
dev@347 77 currentRootIndex,
dev@347 78 Object.assign(
dev@347 79 {},
dev@347 80 this.analyses.get(currentRootIndex),
dev@464 81 {audioData}
dev@347 82 )
dev@347 83 );
dev@347 84 }
dev@193 85 }
dev@193 86 }
dev@193 87 }
dev@193 88 );
dev@228 89 this.onProgressUpdated = this.featureService.progressUpdated$.subscribe(
dev@226 90 progress => {
dev@226 91 const index = this.analyses.findIndex(val => val.id === progress.id);
dev@236 92 if (index === -1) {
dev@236 93 return;
dev@236 94 }
dev@235 95
dev@466 96 this.analyses.setMutating(
dev@235 97 index,
dev@235 98 Object.assign(
dev@235 99 {},
dev@235 100 this.analyses.get(index),
dev@235 101 {progress: progress.value}
dev@235 102 )
dev@235 103 );
dev@226 104 }
dev@226 105 );
dev@48 106 }
dev@16 107
dev@456 108 onFileOpened(file: File | Blob, createExportableItem = false) {
dev@49 109 this.canExtract = false;
dev@203 110 const url = this.audioService.loadAudio(file);
dev@203 111 // TODO is it safe to assume it is a recording?
dev@203 112 const title = (file instanceof File) ?
dev@203 113 (file as File).name : `Recording ${this.nRecordings++}`;
dev@203 114
dev@203 115 if (this.analyses.filter(item => item.title === title).length > 0) {
dev@203 116 // TODO this reveals how brittle the current name / uri based id is
dev@203 117 // need something more robust, and also need to notify the user
dev@203 118 // in a suitable way in the actual event of a duplicate file
dev@203 119 console.warn('There is already a notebook based on this audio file.');
dev@203 120 return;
dev@203 121 }
dev@203 122
dev@350 123 const pending = {
dev@350 124 uri: url,
dev@203 125 hasSharedTimeline: true,
dev@203 126 title: title,
dev@206 127 description: new Date().toLocaleString(),
dev@453 128 id: `${++this.countingId}`,
dev@456 129 mimeType: file.type,
dev@456 130 isExportable: createExportableItem
dev@460 131 } as RootAudioItem;
dev@350 132
dev@350 133 // TODO re-ordering of items for display
dev@350 134 // , one alternative is a Angular Pipe / Filter for use in the Template
dev@466 135 this.analyses.unshiftMutating(pending);
dev@16 136 }
dev@47 137
dev@460 138 extractFeatures(outputInfo: ExtractorOutputInfo): string {
dev@236 139 if (!this.canExtract || !outputInfo) {
dev@236 140 return;
dev@236 141 }
dev@236 142
dev@49 143 this.canExtract = false;
dev@203 144
dev@464 145 const rootAudio = getRootAudioItem(this.analyses.get(0));
dev@464 146
dev@464 147 if (isLoadedRootAudioItem(rootAudio)) {
dev@464 148 const placeholderCard: AnalysisItem = {
dev@464 149 parent: rootAudio,
dev@464 150 hasSharedTimeline: true,
dev@464 151 extractorKey: outputInfo.extractorKey,
dev@464 152 outputId: outputInfo.outputId,
dev@464 153 title: outputInfo.name,
dev@464 154 description: outputInfo.outputId,
dev@464 155 id: `${++this.countingId}`,
dev@464 156 progress: 0
dev@464 157 };
dev@466 158 this.analyses.unshiftMutating(placeholderCard);
dev@464 159 this.sendExtractionRequest(placeholderCard);
dev@464 160 return placeholderCard.id;
dev@464 161 }
dev@464 162 throw new Error('Cannot extract. No audio loaded');
dev@47 163 }
dev@193 164
dev@456 165 removeItem(item: Item): void {
dev@456 166 const indicesToRemove: number[] = this.analyses.reduce(
dev@456 167 (toRemove, current, index) => {
dev@456 168 if (isPendingAnalysisItem(current) && current.parent.id === item.id) {
dev@456 169 toRemove.push(index);
dev@456 170 } else if (item.id === current.id) {
dev@456 171 toRemove.push(index);
dev@456 172 }
dev@456 173 return toRemove;
dev@456 174 }, []);
dev@459 175 this.analyses.remove(...indicesToRemove);
dev@456 176 }
dev@456 177
dev@193 178 ngOnDestroy(): void {
dev@193 179 this.onAudioDataSubscription.unsubscribe();
dev@226 180 this.onProgressUpdated.unsubscribe();
dev@193 181 }
dev@460 182
dev@460 183 private sendExtractionRequest(analysis: AnalysisItem): Promise<void> {
dev@460 184 const findAndUpdateItem = (result: ExtractionResult): void => {
dev@460 185 // TODO subscribe to the extraction service instead
dev@460 186 const i = this.analyses.findIndex(val => val.id === result.id);
dev@460 187 this.canExtract = true;
dev@460 188 if (i !== -1) {
dev@460 189 this.analyses.set(
dev@460 190 i,
dev@460 191 Object.assign(
dev@460 192 {},
dev@460 193 this.analyses.get(i),
dev@460 194 result.result,
dev@460 195 result.unit ? {unit: result.unit} : {}
dev@460 196 )
dev@460 197 );
dev@460 198 } // TODO else remove the item?
dev@460 199 };
dev@460 200 return this.featureService.extract(
dev@460 201 analysis.id,
dev@460 202 createExtractionRequest(analysis))
dev@460 203 .then(findAndUpdateItem)
dev@460 204 .catch(err => {
dev@460 205 this.canExtract = true;
dev@460 206 this.analyses.shift();
dev@460 207 console.error(`Error whilst extracting: ${err}`);
dev@460 208 });
dev@460 209 }
angular-cli@0 210 }