dev@170
|
1 /**
|
dev@170
|
2 * Created by lucast on 21/03/2017.
|
dev@170
|
3 */
|
dev@231
|
4 import {
|
dev@231
|
5 ChangeDetectionStrategy,
|
dev@231
|
6 Component,
|
dev@231
|
7 Input,
|
dev@408
|
8 OnDestroy,
|
dev@231
|
9 OnInit
|
dev@236
|
10 } from '@angular/core';
|
dev@348
|
11 import {naivePagingMapper} from '../visualisations/WavesJunk';
|
dev@408
|
12 import {OnSeekHandler} from '../playhead/PlayHeadHelpers';
|
dev@361
|
13 import {
|
dev@381
|
14 defaultColourGenerator,
|
dev@361
|
15 HigherLevelFeatureShape,
|
dev@361
|
16 KnownShapedFeature
|
dev@361
|
17 } from '../visualisations/FeatureUtilities';
|
dev@408
|
18 import {
|
dev@408
|
19 RenderLoopService,
|
dev@408
|
20 TaskRemover
|
dev@408
|
21 } from '../services/render-loop/render-loop.service';
|
dev@170
|
22
|
dev@350
|
23 export interface Item {
|
dev@350
|
24 id: string;
|
dev@200
|
25 hasSharedTimeline: boolean;
|
dev@200
|
26 title?: string;
|
dev@200
|
27 description?: string;
|
dev@224
|
28 progress?: number;
|
dev@350
|
29 }
|
dev@350
|
30
|
dev@350
|
31 export interface PendingRootAudioItem extends Item {
|
dev@350
|
32 uri: string;
|
dev@453
|
33 mimeType?: string;
|
dev@350
|
34 }
|
dev@378
|
35 export interface RootAudioItem extends PendingRootAudioItem {
|
dev@350
|
36 audioData: AudioBuffer;
|
dev@350
|
37 }
|
dev@350
|
38
|
dev@350
|
39 export interface PendingAnalysisItem extends Item {
|
dev@350
|
40 parent: RootAudioItem;
|
dev@350
|
41 extractorKey: string;
|
dev@350
|
42 }
|
dev@350
|
43
|
dev@396
|
44 export type AnalysisItem = PendingAnalysisItem & KnownShapedFeature & {
|
dev@396
|
45 unit?: string
|
dev@396
|
46 };
|
dev@361
|
47
|
dev@361
|
48 export function isItem(item: Item): item is Item {
|
dev@361
|
49 return item.id != null && item.hasSharedTimeline != null;
|
dev@350
|
50 }
|
dev@350
|
51
|
dev@350
|
52 export function isPendingRootAudioItem(item: Item): item is PendingRootAudioItem {
|
dev@361
|
53 return isItem(item) && typeof (item as RootAudioItem).uri === 'string';
|
dev@350
|
54 }
|
dev@350
|
55
|
dev@350
|
56 export function isRootAudioItem(item: Item): item is RootAudioItem {
|
dev@410
|
57 return item && isPendingRootAudioItem(item) &&
|
dev@351
|
58 (item as RootAudioItem).audioData instanceof AudioBuffer;
|
dev@350
|
59 }
|
dev@350
|
60
|
dev@350
|
61 export function isPendingAnalysisItem(item: Item): item is AnalysisItem {
|
dev@350
|
62 const downcast = (item as AnalysisItem);
|
dev@350
|
63 return isRootAudioItem(downcast.parent)
|
dev@350
|
64 && typeof downcast.extractorKey === 'string';
|
dev@350
|
65 }
|
dev@350
|
66
|
dev@350
|
67 export function isAnalysisItem(item: Item): item is AnalysisItem {
|
dev@350
|
68 const downcast = (item as AnalysisItem);
|
dev@361
|
69 return isPendingAnalysisItem(item) &&
|
dev@361
|
70 downcast.shape != null &&
|
dev@361
|
71 downcast.collected != null;
|
dev@350
|
72 }
|
dev@350
|
73
|
dev@350
|
74 // these should probably be actual concrete types with their own getUri methods
|
dev@350
|
75 export function getRootUri(item: Item): string {
|
dev@350
|
76 if (isPendingRootAudioItem(item)) {
|
dev@350
|
77 return item.uri;
|
dev@350
|
78 }
|
dev@350
|
79 if (isPendingAnalysisItem(item)) {
|
dev@350
|
80 return item.parent.uri;
|
dev@350
|
81 }
|
dev@350
|
82 throw new Error('Invalid item: No URI property set.');
|
dev@170
|
83 }
|
dev@170
|
84
|
dev@170
|
85 @Component({
|
dev@170
|
86 selector: 'ugly-analysis-item',
|
dev@170
|
87 templateUrl: './analysis-item.component.html',
|
dev@231
|
88 styleUrls: ['./analysis-item.component.css'],
|
dev@231
|
89 changeDetection: ChangeDetectionStrategy.OnPush
|
dev@170
|
90 })
|
dev@408
|
91 export class AnalysisItemComponent implements OnInit, OnDestroy {
|
dev@224
|
92
|
dev@408
|
93 // TODO should be TimelineTimeContext?
|
dev@408
|
94 @Input() set timeline(timeline: Timeline) {
|
dev@408
|
95 this.mTimeline = timeline;
|
dev@408
|
96 this.resetRemoveAnimation();
|
dev@408
|
97 }
|
dev@408
|
98
|
dev@408
|
99 get timeline(): Timeline {
|
dev@408
|
100 return this.mTimeline;
|
dev@408
|
101 }
|
dev@408
|
102
|
dev@408
|
103 @Input() set isActive(isActive: boolean) {
|
dev@408
|
104 this.removeAnimation();
|
dev@408
|
105 this.mIsActive = isActive;
|
dev@408
|
106 if (isActive) {
|
dev@408
|
107 this.resetRemoveAnimation();
|
dev@408
|
108 }
|
dev@408
|
109 }
|
dev@408
|
110
|
dev@408
|
111 get isActive() {
|
dev@408
|
112 return this.mIsActive;
|
dev@408
|
113 }
|
dev@408
|
114
|
dev@350
|
115 @Input() item: Item;
|
dev@285
|
116 @Input() contentWidth: number;
|
dev@348
|
117 @Input() onSeek: OnSeekHandler;
|
dev@408
|
118 // TODO move / re-think - naivePagingMapper feels like a big ol' bodge
|
dev@408
|
119 private removeAnimation: TaskRemover;
|
dev@224
|
120 private hasProgressOnInit = false;
|
dev@408
|
121 private mIsActive: boolean;
|
dev@408
|
122 private mTimeline: Timeline;
|
dev@224
|
123
|
dev@408
|
124 constructor(private renderLoop: RenderLoopService) {}
|
dev@348
|
125
|
dev@224
|
126 ngOnInit(): void {
|
dev@408
|
127 this.resetRemoveAnimation();
|
dev@231
|
128 this.hasProgressOnInit = this.item.progress != null;
|
dev@224
|
129 }
|
dev@224
|
130
|
dev@224
|
131 isLoading(): boolean {
|
dev@231
|
132 return this.hasProgressOnInit && this.item.progress < 100;
|
dev@224
|
133 }
|
dev@348
|
134
|
dev@348
|
135 isAudioItem(): boolean {
|
dev@408
|
136 return this.item && isRootAudioItem(this.item);
|
dev@348
|
137 }
|
dev@361
|
138
|
dev@410
|
139 isPending(): boolean {
|
dev@410
|
140 return this.item &&
|
dev@410
|
141 !isRootAudioItem(this.item) && !isAnalysisItem(this.item) &&
|
dev@410
|
142 (isPendingAnalysisItem(this.item) || isPendingRootAudioItem(this.item));
|
dev@410
|
143 }
|
dev@410
|
144
|
dev@361
|
145 getFeatureShape(): HigherLevelFeatureShape | null {
|
dev@361
|
146 return !isPendingRootAudioItem(this.item) &&
|
dev@361
|
147 isAnalysisItem(this.item) ? this.item.shape : null;
|
dev@361
|
148 }
|
dev@381
|
149
|
dev@412
|
150 getDuration(): number | null {
|
dev@412
|
151 if (isRootAudioItem(this.item)) {
|
dev@412
|
152 return this.item.audioData.duration;
|
dev@412
|
153 }
|
dev@412
|
154 if (isAnalysisItem(this.item)) {
|
dev@412
|
155 return this.item.parent.audioData.duration;
|
dev@412
|
156 }
|
dev@412
|
157 }
|
dev@412
|
158
|
dev@381
|
159 getNextColour(): string {
|
dev@381
|
160 return defaultColourGenerator.next().value;
|
dev@381
|
161 }
|
dev@408
|
162
|
dev@408
|
163 ngOnDestroy(): void {
|
dev@408
|
164 this.removeAnimation();
|
dev@408
|
165 }
|
dev@408
|
166
|
dev@408
|
167 private resetRemoveAnimation(): void {
|
dev@408
|
168 if (this.removeAnimation) {
|
dev@408
|
169 this.removeAnimation();
|
dev@408
|
170 }
|
dev@408
|
171 const createPagingTask = () => {
|
dev@408
|
172 const pagingMapper = naivePagingMapper(this.timeline);
|
dev@408
|
173 return this.renderLoop.addPlayingTask(currentTime => {
|
dev@408
|
174 pagingMapper(currentTime);
|
dev@408
|
175 });
|
dev@408
|
176 };
|
dev@408
|
177 // only add a pager to audio items, it can drive the feature items
|
dev@408
|
178 const remover = this.timeline && this.isAudioItem() ?
|
dev@408
|
179 createPagingTask() : () => {};
|
dev@408
|
180 this.removeAnimation = () => {
|
dev@408
|
181 remover();
|
dev@408
|
182 this.removeAnimation = () => {};
|
dev@408
|
183 };
|
dev@408
|
184 }
|
dev@170
|
185 }
|