comparison src/app/app.component.ts @ 456:7bb0bac6f8dc

Add export button for recordings and option to remove audio item (also removes all related analyses atm). Revokes associated object url for audio on removal. Will be problematic if the history is used for undo / redo.
author Lucas Thompson <dev@lucas.im>
date Thu, 29 Jun 2017 20:11:14 +0100
parents 8113b6f5a75e
children 906dd152e333
comparison
equal deleted inserted replaced
455:d27f1ca7ba6a 456:7bb0bac6f8dc
1 import {Component, OnDestroy} from '@angular/core'; 1 import {Component, Inject, OnDestroy} from '@angular/core';
2 import { 2 import {
3 AudioPlayerService, 3 AudioPlayerService,
4 AudioResourceError, AudioResource 4 AudioResourceError,
5 AudioResource
5 } from './services/audio-player/audio-player.service'; 6 } from './services/audio-player/audio-player.service';
6 import {FeatureExtractionService} from './services/feature-extraction/feature-extraction.service'; 7 import {FeatureExtractionService} from './services/feature-extraction/feature-extraction.service';
7 import {ExtractorOutputInfo} from './feature-extraction-menu/feature-extraction-menu.component'; 8 import {ExtractorOutputInfo} from './feature-extraction-menu/feature-extraction-menu.component';
8 import {DomSanitizer} from '@angular/platform-browser'; 9 import {DomSanitizer} from '@angular/platform-browser';
9 import {MdIconRegistry} from '@angular/material'; 10 import {MdIconRegistry} from '@angular/material';
10 import {Subscription} from 'rxjs/Subscription'; 11 import {Subscription} from 'rxjs/Subscription';
11 import { 12 import {
12 AnalysisItem, 13 AnalysisItem,
14 isPendingAnalysisItem, isPendingRootAudioItem,
13 isRootAudioItem, 15 isRootAudioItem,
14 Item, PendingAnalysisItem, PendingRootAudioItem, RootAudioItem 16 Item,
17 PendingAnalysisItem,
18 PendingRootAudioItem,
19 RootAudioItem
15 } from './analysis-item/analysis-item.component'; 20 } from './analysis-item/analysis-item.component';
16 import {OnSeekHandler} from './playhead/PlayHeadHelpers'; 21 import {OnSeekHandler} from './playhead/PlayHeadHelpers';
22 import {UrlResourceLifetimeManager} from './app.module';
17 23
18 class PersistentStack<T> { 24 class PersistentStack<T> {
19 private stack: T[]; 25 private stack: T[];
20 private history: T[][]; 26 private history: T[][];
21 27
56 this.stack = [ 62 this.stack = [
57 ...this.stack.slice(0, index), 63 ...this.stack.slice(0, index),
58 value, 64 value,
59 ...this.stack.slice(index + 1) 65 ...this.stack.slice(index + 1)
60 ]; 66 ];
67 }
68
69 map<U>(transform: (value: T, index: number, array: T[]) => U): U[] {
70 return this.stack.map(transform);
71 }
72
73 reduce<U>(reducer: (previousValue: U,
74 currentValue: T,
75 currentIndex: number,
76 array: T[]) => U,
77 initialValue: U): U {
78 return this.stack.reduce(reducer, initialValue);
79 }
80
81 remove(...indices: number[]) {
82 this.history.push([...this.stack]);
83 this.stack = this.stack.reduce((acc, item, i) => {
84 if (!indices.includes(i)) {
85 acc.push(item);
86 }
87 return acc;
88 }, [] as T[]);
61 } 89 }
62 90
63 toIterable(): Iterable<T> { 91 toIterable(): Iterable<T> {
64 return this.stack; 92 return this.stack;
65 } 93 }
82 private onSeek: OnSeekHandler; 110 private onSeek: OnSeekHandler;
83 111
84 constructor(private audioService: AudioPlayerService, 112 constructor(private audioService: AudioPlayerService,
85 private featureService: FeatureExtractionService, 113 private featureService: FeatureExtractionService,
86 private iconRegistry: MdIconRegistry, 114 private iconRegistry: MdIconRegistry,
87 private sanitizer: DomSanitizer) { 115 private sanitizer: DomSanitizer,
116 @Inject(
117 'UrlResourceLifetimeManager'
118 ) private resourceManager: UrlResourceLifetimeManager) {
88 this.analyses = new PersistentStack<AnalysisItem>(); 119 this.analyses = new PersistentStack<AnalysisItem>();
89 this.canExtract = false; 120 this.canExtract = false;
90 this.nRecordings = 0; 121 this.nRecordings = 0;
91 this.countingId = 0; 122 this.countingId = 0;
92 this.onSeek = (time) => this.audioService.seekTo(time); 123 this.onSeek = (time) => this.audioService.seekTo(time);
142 ); 173 );
143 } 174 }
144 ); 175 );
145 } 176 }
146 177
147 onFileOpened(file: File | Blob) { 178 onFileOpened(file: File | Blob, createExportableItem = false) {
148 this.canExtract = false; 179 this.canExtract = false;
149 const url = this.audioService.loadAudio(file); 180 const url = this.audioService.loadAudio(file);
150 // TODO is it safe to assume it is a recording? 181 // TODO is it safe to assume it is a recording?
151 const title = (file instanceof File) ? 182 const title = (file instanceof File) ?
152 (file as File).name : `Recording ${this.nRecordings++}`; 183 (file as File).name : `Recording ${this.nRecordings++}`;
163 uri: url, 194 uri: url,
164 hasSharedTimeline: true, 195 hasSharedTimeline: true,
165 title: title, 196 title: title,
166 description: new Date().toLocaleString(), 197 description: new Date().toLocaleString(),
167 id: `${++this.countingId}`, 198 id: `${++this.countingId}`,
168 mimeType: file.type 199 mimeType: file.type,
200 isExportable: createExportableItem
169 } as PendingRootAudioItem; 201 } as PendingRootAudioItem;
170 this.rootAudioItem = pending as RootAudioItem; // TODO this is silly 202 this.rootAudioItem = pending as RootAudioItem; // TODO this is silly
171 203
172 // TODO re-ordering of items for display 204 // TODO re-ordering of items for display
173 // , one alternative is a Angular Pipe / Filter for use in the Template 205 // , one alternative is a Angular Pipe / Filter for use in the Template
221 this.analyses.shift(); 253 this.analyses.shift();
222 console.error(`Error whilst extracting: ${err}`); 254 console.error(`Error whilst extracting: ${err}`);
223 }); 255 });
224 } 256 }
225 257
258 removeItem(item: Item): void {
259 const indicesToRemove: number[] = this.analyses.reduce(
260 (toRemove, current, index) => {
261 if (isPendingAnalysisItem(current) && current.parent.id === item.id) {
262 toRemove.push(index);
263 } else if (item.id === current.id) {
264 toRemove.push(index);
265 }
266 return toRemove;
267 }, []);
268 if (isPendingRootAudioItem(item)) {
269 this.resourceManager.revokeUrlToResource(item.uri);
270 }
271 this.analyses.remove(...indicesToRemove);
272 }
273
226 ngOnDestroy(): void { 274 ngOnDestroy(): void {
227 this.onAudioDataSubscription.unsubscribe(); 275 this.onAudioDataSubscription.unsubscribe();
228 this.onProgressUpdated.unsubscribe(); 276 this.onProgressUpdated.unsubscribe();
229 } 277 }
230 } 278 }