dev@170: /** dev@170: * Created by lucast on 21/03/2017. dev@170: */ dev@231: import { dev@231: ChangeDetectionStrategy, dev@231: Component, dev@231: Input, dev@408: OnDestroy, dev@231: OnInit dev@236: } from '@angular/core'; dev@348: import {naivePagingMapper} from '../visualisations/WavesJunk'; dev@408: import {OnSeekHandler} from '../playhead/PlayHeadHelpers'; dev@361: import { dev@381: defaultColourGenerator, dev@361: HigherLevelFeatureShape, dev@361: KnownShapedFeature dev@361: } from '../visualisations/FeatureUtilities'; dev@408: import { dev@408: RenderLoopService, dev@408: TaskRemover dev@408: } from '../services/render-loop/render-loop.service'; dev@170: dev@350: export interface Item { dev@350: id: string; dev@200: hasSharedTimeline: boolean; dev@200: title?: string; dev@200: description?: string; dev@224: progress?: number; dev@350: } dev@350: dev@350: export interface PendingRootAudioItem extends Item { dev@350: uri: string; dev@453: mimeType?: string; dev@350: } dev@378: export interface RootAudioItem extends PendingRootAudioItem { dev@350: audioData: AudioBuffer; dev@350: } dev@350: dev@350: export interface PendingAnalysisItem extends Item { dev@350: parent: RootAudioItem; dev@350: extractorKey: string; dev@350: } dev@350: dev@396: export type AnalysisItem = PendingAnalysisItem & KnownShapedFeature & { dev@396: unit?: string dev@396: }; dev@361: dev@361: export function isItem(item: Item): item is Item { dev@361: return item.id != null && item.hasSharedTimeline != null; dev@350: } dev@350: dev@350: export function isPendingRootAudioItem(item: Item): item is PendingRootAudioItem { dev@361: return isItem(item) && typeof (item as RootAudioItem).uri === 'string'; dev@350: } dev@350: dev@350: export function isRootAudioItem(item: Item): item is RootAudioItem { dev@410: return item && isPendingRootAudioItem(item) && dev@351: (item as RootAudioItem).audioData instanceof AudioBuffer; dev@350: } dev@350: dev@350: export function isPendingAnalysisItem(item: Item): item is AnalysisItem { dev@350: const downcast = (item as AnalysisItem); dev@350: return isRootAudioItem(downcast.parent) dev@350: && typeof downcast.extractorKey === 'string'; dev@350: } dev@350: dev@350: export function isAnalysisItem(item: Item): item is AnalysisItem { dev@350: const downcast = (item as AnalysisItem); dev@361: return isPendingAnalysisItem(item) && dev@361: downcast.shape != null && dev@361: downcast.collected != null; dev@350: } dev@350: dev@350: // these should probably be actual concrete types with their own getUri methods dev@350: export function getRootUri(item: Item): string { dev@350: if (isPendingRootAudioItem(item)) { dev@350: return item.uri; dev@350: } dev@350: if (isPendingAnalysisItem(item)) { dev@350: return item.parent.uri; dev@350: } dev@350: throw new Error('Invalid item: No URI property set.'); dev@170: } dev@170: dev@170: @Component({ dev@170: selector: 'ugly-analysis-item', dev@170: templateUrl: './analysis-item.component.html', dev@231: styleUrls: ['./analysis-item.component.css'], dev@231: changeDetection: ChangeDetectionStrategy.OnPush dev@170: }) dev@408: export class AnalysisItemComponent implements OnInit, OnDestroy { dev@224: dev@408: // TODO should be TimelineTimeContext? dev@408: @Input() set timeline(timeline: Timeline) { dev@408: this.mTimeline = timeline; dev@408: this.resetRemoveAnimation(); dev@408: } dev@408: dev@408: get timeline(): Timeline { dev@408: return this.mTimeline; dev@408: } dev@408: dev@408: @Input() set isActive(isActive: boolean) { dev@408: this.removeAnimation(); dev@408: this.mIsActive = isActive; dev@408: if (isActive) { dev@408: this.resetRemoveAnimation(); dev@408: } dev@408: } dev@408: dev@408: get isActive() { dev@408: return this.mIsActive; dev@408: } dev@408: dev@350: @Input() item: Item; dev@285: @Input() contentWidth: number; dev@348: @Input() onSeek: OnSeekHandler; dev@408: // TODO move / re-think - naivePagingMapper feels like a big ol' bodge dev@408: private removeAnimation: TaskRemover; dev@224: private hasProgressOnInit = false; dev@408: private mIsActive: boolean; dev@408: private mTimeline: Timeline; dev@224: dev@408: constructor(private renderLoop: RenderLoopService) {} dev@348: dev@224: ngOnInit(): void { dev@408: this.resetRemoveAnimation(); dev@231: this.hasProgressOnInit = this.item.progress != null; dev@224: } dev@224: dev@224: isLoading(): boolean { dev@231: return this.hasProgressOnInit && this.item.progress < 100; dev@224: } dev@348: dev@348: isAudioItem(): boolean { dev@408: return this.item && isRootAudioItem(this.item); dev@348: } dev@361: dev@410: isPending(): boolean { dev@410: return this.item && dev@410: !isRootAudioItem(this.item) && !isAnalysisItem(this.item) && dev@410: (isPendingAnalysisItem(this.item) || isPendingRootAudioItem(this.item)); dev@410: } dev@410: dev@361: getFeatureShape(): HigherLevelFeatureShape | null { dev@361: return !isPendingRootAudioItem(this.item) && dev@361: isAnalysisItem(this.item) ? this.item.shape : null; dev@361: } dev@381: dev@412: getDuration(): number | null { dev@412: if (isRootAudioItem(this.item)) { dev@412: return this.item.audioData.duration; dev@412: } dev@412: if (isAnalysisItem(this.item)) { dev@412: return this.item.parent.audioData.duration; dev@412: } dev@412: } dev@412: dev@381: getNextColour(): string { dev@381: return defaultColourGenerator.next().value; dev@381: } dev@408: dev@408: ngOnDestroy(): void { dev@408: this.removeAnimation(); dev@408: } dev@408: dev@408: private resetRemoveAnimation(): void { dev@408: if (this.removeAnimation) { dev@408: this.removeAnimation(); dev@408: } dev@408: const createPagingTask = () => { dev@408: const pagingMapper = naivePagingMapper(this.timeline); dev@408: return this.renderLoop.addPlayingTask(currentTime => { dev@408: pagingMapper(currentTime); dev@408: }); dev@408: }; dev@408: // only add a pager to audio items, it can drive the feature items dev@408: const remover = this.timeline && this.isAudioItem() ? dev@408: createPagingTask() : () => {}; dev@408: this.removeAnimation = () => { dev@408: remover(); dev@408: this.removeAnimation = () => {}; dev@408: }; dev@408: } dev@170: }