/
av-timeline.component.ts
233 lines (184 loc) · 6.36 KB
/
av-timeline.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import { CdkDragMove } from '@angular/cdk/drag-drop';
import {
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
Output,
SimpleChange,
ViewChild
} from '@angular/core';
export interface PointerValue {
position: number;
time: number;
}
/**
* this component can be used in video and audio players
*/
@Component({
selector: 'app-av-timeline',
templateUrl: './av-timeline.component.html',
styleUrls: ['./av-timeline.component.scss']
})
export class AvTimelineComponent implements OnChanges {
// current time value
@Input() value: number;
// start time value
@Input() min?= 0;
// end time value: Normally this is the duration
@Input() max: number;
// in case parent resized: Will be used in video player when switching between cinema and default view
@Input() resized: boolean;
// send click position to parent
@Output() changed = new EventEmitter<number>();
// send mouse position to parent
@Output() move = new EventEmitter<PointerValue>();
// send dimension and position of the timeline
@Output() dimension = new EventEmitter<DOMRect>();
// timeline element: main container
@ViewChild('timeline') timelineEle: ElementRef;
// progress element: thin bar line
@ViewChild('progress') progressEle: ElementRef;
// thumb element: current postion pointer
@ViewChild('thumb') thumbEle: ElementRef;
// in case of draging the thumb element
dragging = false;
// size of timeline; will be used to calculate progress position in pixel corresponding to time value
timelineDimension: DOMRect | null = null;
constructor() {
}
@HostListener('mouseenter', ['$event']) onEnter(e: MouseEvent) {
this._onMouseenter(e);
}
@HostListener('mousemove', ['$event']) onMousemove(e: MouseEvent) {
this._onMousemove(e);
}
@HostListener('mouseup', ['$event']) onMouseup(e: MouseEvent) {
this._onMouseup(e);
}
@HostListener('window:resize', ['$event']) onWindowResize(e: Event) {
this._onWindowResize(e);
}
ngOnChanges(changes: { [propName: string]: SimpleChange }) {
if (!this.timelineEle && !this.progressEle) {
return;
}
if (!this.timelineDimension) {
// calculate timeline dimension if it doesn't exist
this.timelineDimension = this._getTimelineDimensions();
} else {
// recalculate timeline dimension because resized parameter has changed
if (changes.resized) {
this.timelineDimension = this._getResizedTimelineDimensions();
}
}
// emit the dimension to the parent
this.dimension.emit(this.timelineDimension);
// update pointer position from time
this.updatePositionFromTime(this.value);
}
/**
* updates position from time value
* @param time
*/
updatePositionFromTime(time: number) {
// calc position on the x axis from time value
const percent: number = (time / this.max);
const pos: number = this.timelineDimension.width * percent;
this.updatePosition(pos);
}
/**
* updates position of the thumb
* @param pos
*/
updatePosition(pos: number) {
// already played time: fill with red background color
const fillPos = (pos / this.timelineDimension.width);
// background (timeline fill) start position
const bgPos = (1 - fillPos);
if (!this.dragging) {
// update thumb position if not dragging
this.thumbEle.nativeElement.style.transform = 'translateX(' + pos + 'px) scale(.7)';
}
// adjust progress width / fill already played time
this.progressEle.nativeElement.children[0].style.transform = 'translateX(0px) scale3d(' + bgPos + ', 1, 1)';
// adjust progress width / progress background
this.progressEle.nativeElement.children[2].style.transform = 'translateX(0px) scale3d(' + fillPos + ', 1, 1)';
}
/**
* toggles dragging
*/
toggleDragging() {
this.dragging = !this.dragging;
}
/**
* drags action
* @param ev
*/
dragAction(ev: CdkDragMove) {
const pos: number = (ev.pointerPosition.x - this.timelineDimension.left);
this.updatePosition(pos);
}
/**
* mouse enters timeline
*/
private _onMouseenter(ev: MouseEvent) {
this.timelineDimension = this._getTimelineDimensions();
}
/**
* mouse moves on timeline
*/
private _onMousemove(ev: MouseEvent) {
const pos: number = ev.clientX - this.timelineDimension.left;
const percent: number = pos / this.timelineDimension.width;
let time: number = (percent * this.max);
if (time < 0) {
time = 0;
} else if (time > this.max) {
time = this.max;
}
this.move.emit({ position: ev.clientX, time });
}
/**
* determines action after click or drop event
* @param ev
*/
private _onMouseup(ev: MouseEvent) {
const pos: number = (ev.clientX - this.timelineDimension.left);
this.updatePosition(pos);
const percentage: number = (pos / this.timelineDimension.width);
// calc time value to submit to parent
const time: number = (percentage * this.max);
this.changed.emit(time);
}
/**
* event listener on window resize
*/
private _onWindowResize(ev: Event) {
this.timelineDimension = this._getResizedTimelineDimensions();
this.dimension.emit(this.timelineDimension);
}
/**
* get the bounding client rect of the slider track element.
* The track is used rather than the native element to ignore the extra space that the thumb can
* take up.
*/
private _getTimelineDimensions(): DOMRect | null {
return this.timelineEle ? this.timelineEle.nativeElement.getBoundingClientRect() : null;
}
/**
* gets resized timeline dimensions
* @returns resized timeline dimensions
*/
private _getResizedTimelineDimensions(): DOMRect | null {
// recalculate timeline dimension
const newDimension: DOMRect = this._getTimelineDimensions();
if (this.timelineDimension.width !== newDimension.width) {
return newDimension;
} else {
return;
}
}
}