Mercurial > hg > ugly-duckling
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 } |