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@350
|
33 }
|
dev@378
|
34 export interface RootAudioItem extends PendingRootAudioItem {
|
dev@350
|
35 audioData: AudioBuffer;
|
dev@350
|
36 }
|
dev@350
|
37
|
dev@350
|
38 export interface PendingAnalysisItem extends Item {
|
dev@350
|
39 parent: RootAudioItem;
|
dev@350
|
40 extractorKey: string;
|
dev@350
|
41 }
|
dev@350
|
42
|
dev@396
|
43 export type AnalysisItem = PendingAnalysisItem & KnownShapedFeature & {
|
dev@396
|
44 unit?: string
|
dev@396
|
45 };
|
dev@361
|
46
|
dev@361
|
47 export function isItem(item: Item): item is Item {
|
dev@361
|
48 return item.id != null && item.hasSharedTimeline != null;
|
dev@350
|
49 }
|
dev@350
|
50
|
dev@350
|
51 export function isPendingRootAudioItem(item: Item): item is PendingRootAudioItem {
|
dev@361
|
52 return isItem(item) && typeof (item as RootAudioItem).uri === 'string';
|
dev@350
|
53 }
|
dev@350
|
54
|
dev@350
|
55 export function isRootAudioItem(item: Item): item is RootAudioItem {
|
dev@350
|
56 return isPendingRootAudioItem(item) &&
|
dev@351
|
57 (item as RootAudioItem).audioData instanceof AudioBuffer;
|
dev@350
|
58 }
|
dev@350
|
59
|
dev@350
|
60 export function isPendingAnalysisItem(item: Item): item is AnalysisItem {
|
dev@350
|
61 const downcast = (item as AnalysisItem);
|
dev@350
|
62 return isRootAudioItem(downcast.parent)
|
dev@350
|
63 && typeof downcast.extractorKey === 'string';
|
dev@350
|
64 }
|
dev@350
|
65
|
dev@350
|
66 export function isAnalysisItem(item: Item): item is AnalysisItem {
|
dev@350
|
67 const downcast = (item as AnalysisItem);
|
dev@361
|
68 return isPendingAnalysisItem(item) &&
|
dev@361
|
69 downcast.shape != null &&
|
dev@361
|
70 downcast.collected != null;
|
dev@350
|
71 }
|
dev@350
|
72
|
dev@350
|
73 // these should probably be actual concrete types with their own getUri methods
|
dev@350
|
74 export function getRootUri(item: Item): string {
|
dev@350
|
75 if (isPendingRootAudioItem(item)) {
|
dev@350
|
76 return item.uri;
|
dev@350
|
77 }
|
dev@350
|
78 if (isPendingAnalysisItem(item)) {
|
dev@350
|
79 return item.parent.uri;
|
dev@350
|
80 }
|
dev@350
|
81 throw new Error('Invalid item: No URI property set.');
|
dev@170
|
82 }
|
dev@170
|
83
|
dev@170
|
84 @Component({
|
dev@170
|
85 selector: 'ugly-analysis-item',
|
dev@170
|
86 templateUrl: './analysis-item.component.html',
|
dev@231
|
87 styleUrls: ['./analysis-item.component.css'],
|
dev@231
|
88 changeDetection: ChangeDetectionStrategy.OnPush
|
dev@170
|
89 })
|
dev@408
|
90 export class AnalysisItemComponent implements OnInit, OnDestroy {
|
dev@224
|
91
|
dev@408
|
92 // TODO should be TimelineTimeContext?
|
dev@408
|
93 @Input() set timeline(timeline: Timeline) {
|
dev@408
|
94 this.mTimeline = timeline;
|
dev@408
|
95 this.resetRemoveAnimation();
|
dev@408
|
96 }
|
dev@408
|
97
|
dev@408
|
98 get timeline(): Timeline {
|
dev@408
|
99 return this.mTimeline;
|
dev@408
|
100 }
|
dev@408
|
101
|
dev@408
|
102 @Input() set isActive(isActive: boolean) {
|
dev@408
|
103 this.removeAnimation();
|
dev@408
|
104 this.mIsActive = isActive;
|
dev@408
|
105 if (isActive) {
|
dev@408
|
106 this.resetRemoveAnimation();
|
dev@408
|
107 }
|
dev@408
|
108 }
|
dev@408
|
109
|
dev@408
|
110 get isActive() {
|
dev@408
|
111 return this.mIsActive;
|
dev@408
|
112 }
|
dev@408
|
113
|
dev@350
|
114 @Input() item: Item;
|
dev@285
|
115 @Input() contentWidth: number;
|
dev@348
|
116 @Input() onSeek: OnSeekHandler;
|
dev@408
|
117 // TODO move / re-think - naivePagingMapper feels like a big ol' bodge
|
dev@408
|
118 private removeAnimation: TaskRemover;
|
dev@224
|
119 private hasProgressOnInit = false;
|
dev@408
|
120 private mIsActive: boolean;
|
dev@408
|
121 private mTimeline: Timeline;
|
dev@224
|
122
|
dev@408
|
123 constructor(private renderLoop: RenderLoopService) {}
|
dev@348
|
124
|
dev@224
|
125 ngOnInit(): void {
|
dev@408
|
126 this.resetRemoveAnimation();
|
dev@231
|
127 this.hasProgressOnInit = this.item.progress != null;
|
dev@224
|
128 }
|
dev@224
|
129
|
dev@224
|
130 isLoading(): boolean {
|
dev@231
|
131 return this.hasProgressOnInit && this.item.progress < 100;
|
dev@224
|
132 }
|
dev@348
|
133
|
dev@348
|
134 isAudioItem(): boolean {
|
dev@408
|
135 return this.item && isRootAudioItem(this.item);
|
dev@348
|
136 }
|
dev@361
|
137
|
dev@361
|
138 getFeatureShape(): HigherLevelFeatureShape | null {
|
dev@361
|
139 return !isPendingRootAudioItem(this.item) &&
|
dev@361
|
140 isAnalysisItem(this.item) ? this.item.shape : null;
|
dev@361
|
141 }
|
dev@381
|
142
|
dev@381
|
143 getNextColour(): string {
|
dev@381
|
144 return defaultColourGenerator.next().value;
|
dev@381
|
145 }
|
dev@408
|
146
|
dev@408
|
147 ngOnDestroy(): void {
|
dev@408
|
148 this.removeAnimation();
|
dev@408
|
149 }
|
dev@408
|
150
|
dev@408
|
151 private resetRemoveAnimation(): void {
|
dev@408
|
152 if (this.removeAnimation) {
|
dev@408
|
153 this.removeAnimation();
|
dev@408
|
154 }
|
dev@408
|
155 const createPagingTask = () => {
|
dev@408
|
156 const pagingMapper = naivePagingMapper(this.timeline);
|
dev@408
|
157 return this.renderLoop.addPlayingTask(currentTime => {
|
dev@408
|
158 pagingMapper(currentTime);
|
dev@408
|
159 });
|
dev@408
|
160 };
|
dev@408
|
161 // only add a pager to audio items, it can drive the feature items
|
dev@408
|
162 const remover = this.timeline && this.isAudioItem() ?
|
dev@408
|
163 createPagingTask() : () => {};
|
dev@408
|
164 this.removeAnimation = () => {
|
dev@408
|
165 remover();
|
dev@408
|
166 this.removeAnimation = () => {};
|
dev@408
|
167 };
|
dev@408
|
168 }
|
dev@170
|
169 }
|