lexgui 0.7.8 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/extensions/codeeditor.js +402 -321
- package/build/extensions/timeline.js +72 -19
- package/build/extensions/videoeditor.js +300 -170
- package/build/lexgui.css +33 -5
- package/build/lexgui.js +164 -96
- package/build/lexgui.min.css +2 -2
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +188 -120
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +43 -1
- package/examples/area-tabs.html +1 -1
- package/examples/asset-view.html +27 -1
- package/examples/timeline.html +23 -13
- package/examples/video-editor.html +152 -3
- package/examples/video-editor2.html +5 -5
- package/package.json +1 -1
|
@@ -23,6 +23,7 @@ class TimeBar {
|
|
|
23
23
|
constructor( area, type, options = {} ) {
|
|
24
24
|
|
|
25
25
|
this.type = type;
|
|
26
|
+
this.duration = options.duration || 1.0;
|
|
26
27
|
|
|
27
28
|
// Create canvas
|
|
28
29
|
this.canvas = document.createElement( 'canvas' );
|
|
@@ -31,10 +32,10 @@ class TimeBar {
|
|
|
31
32
|
area.attach( this.canvas );
|
|
32
33
|
|
|
33
34
|
this.ctx = this.canvas.getContext("2d");
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
this.markerWidth = options.markerWidth ?? 8;
|
|
36
37
|
this.markerHeight = options.markerHeight ?? (this.canvas.height * 0.5);
|
|
37
|
-
this.offset = options.offset || (this.markerWidth*0.5 +
|
|
38
|
+
this.offset = options.offset || (this.markerWidth*0.5 + 5);
|
|
38
39
|
|
|
39
40
|
// dimensions of line (not canvas)
|
|
40
41
|
this.lineWidth = this.canvas.width - this.offset * 2;
|
|
@@ -52,17 +53,75 @@ class TimeBar {
|
|
|
52
53
|
// Retrieve again the color using LX.getThemeColor, which checks the applied theme
|
|
53
54
|
this.updateTheme();
|
|
54
55
|
} )
|
|
56
|
+
|
|
57
|
+
this.canvas.onmousedown = (e) => this.onMouseDown(e);
|
|
58
|
+
this.canvas.onmousemove = (e) => this.onMouseMove(e);
|
|
59
|
+
this.canvas.onmouseup = (e) => this.onMouseUp(e);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
updateTheme(){
|
|
62
|
+
updateTheme( ) {
|
|
58
63
|
TimeBar.BACKGROUND_COLOR = LX.getThemeColor("global-color-secondary");
|
|
59
64
|
TimeBar.COLOR = LX.getThemeColor("global-color-quaternary");
|
|
60
65
|
TimeBar.ACTIVE_COLOR = "#668ee4";
|
|
61
66
|
}
|
|
67
|
+
|
|
68
|
+
setDuration( duration ) {
|
|
69
|
+
this.duration = duration;
|
|
70
|
+
}
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
xToTime( x ) {
|
|
73
|
+
return ((x - this.offset) / (this.lineWidth)) * this.duration;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
timeToX( time ) {
|
|
77
|
+
return (time / this.duration) * (this.lineWidth) + this.offset;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setCurrentTime( time ) {
|
|
81
|
+
this.currentX = this.timeToX( time );
|
|
82
|
+
this.onSetCurrentValue( this.currentX );
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setStartTime( time ) {
|
|
86
|
+
this.startX = this.timeToX( time );
|
|
87
|
+
this.onSetStartValue( this.startX )
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setEndTime( time ) {
|
|
91
|
+
this.endX = this.timeToX( time );
|
|
92
|
+
this.onSetEndValue( this.endX );
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onSetCurrentValue( x ) {
|
|
96
|
+
this.update( x );
|
|
97
|
+
|
|
98
|
+
const t = this.xToTime( x );
|
|
99
|
+
if( this.onChangeCurrent ) {
|
|
100
|
+
this.onChangeCurrent( t );
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onSetStartValue( x ) {
|
|
105
|
+
this.update( x );
|
|
65
106
|
|
|
107
|
+
const t = this.xToTime( x );
|
|
108
|
+
if( this.onChangeStart ) {
|
|
109
|
+
this.onChangeStart( t );
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onSetEndValue( x ) {
|
|
114
|
+
this.update( x );
|
|
115
|
+
|
|
116
|
+
const t = this.xToTime( x );
|
|
117
|
+
if( this.onChangeEnd ) {
|
|
118
|
+
this.onChangeEnd( t );
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_draw( ) {
|
|
123
|
+
const ctx = this.ctx;
|
|
124
|
+
|
|
66
125
|
ctx.save();
|
|
67
126
|
ctx.fillStyle = TimeBar.BACKGROUND_COLOR;
|
|
68
127
|
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
@@ -82,6 +141,10 @@ class TimeBar {
|
|
|
82
141
|
this._drawTrimMarker('start', this.startX, { color: null, fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9'});
|
|
83
142
|
this._drawTrimMarker('end', this.endX, { color: null, fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9'});
|
|
84
143
|
this._drawTimeMarker('current', this.currentX, { color: '#e5e5e5', fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9', width: this.markerWidth });
|
|
144
|
+
|
|
145
|
+
if( this.onDraw ) {
|
|
146
|
+
this.onDraw();
|
|
147
|
+
}
|
|
85
148
|
}
|
|
86
149
|
|
|
87
150
|
_drawTrimMarker(name, x, options) {
|
|
@@ -147,7 +210,7 @@ class TimeBar {
|
|
|
147
210
|
ctx.fillStyle = ctx.strokeStyle = options.fillColor || '#111' // "#FFF";
|
|
148
211
|
|
|
149
212
|
|
|
150
|
-
y -= this.offset +
|
|
213
|
+
y -= this.offset + 8;
|
|
151
214
|
// Current time ball grab
|
|
152
215
|
ctx.fillStyle = options.fillColor || '#e5e5e5';
|
|
153
216
|
ctx.beginPath();
|
|
@@ -157,20 +220,20 @@ class TimeBar {
|
|
|
157
220
|
ctx.shadowBlur = 0;
|
|
158
221
|
}
|
|
159
222
|
|
|
160
|
-
update
|
|
223
|
+
update( x ) {
|
|
161
224
|
this.currentX = Math.min(Math.max(this.startX, x), this.endX);
|
|
162
225
|
this._draw();
|
|
163
|
-
|
|
164
|
-
if(this.onDraw) {
|
|
165
|
-
this.onDraw();
|
|
166
|
-
}
|
|
167
226
|
}
|
|
168
227
|
|
|
169
|
-
onMouseDown
|
|
228
|
+
onMouseDown( e ) {
|
|
229
|
+
|
|
230
|
+
if( this.onMouse ) {
|
|
231
|
+
this.onMouse( e );
|
|
232
|
+
}
|
|
170
233
|
|
|
171
234
|
e.preventDefault();
|
|
172
235
|
|
|
173
|
-
if(!this.canvas || e.target != this.canvas) {
|
|
236
|
+
if( !this.canvas || e.target != this.canvas || e.cancelBubble ) {
|
|
174
237
|
return;
|
|
175
238
|
}
|
|
176
239
|
const canvas = this.canvas;
|
|
@@ -183,43 +246,46 @@ class TimeBar {
|
|
|
183
246
|
const threshold = this.markerWidth;
|
|
184
247
|
|
|
185
248
|
// grab trim markers only from the bottom
|
|
186
|
-
if(Math.abs(this.startX - x) < threshold && this.position.y < y) {
|
|
249
|
+
if( Math.abs(this.startX - x) < threshold && this.position.y < y ) {
|
|
187
250
|
this.dragging = 'start';
|
|
188
251
|
canvas.style.cursor = "grabbing";
|
|
189
252
|
}
|
|
190
|
-
else if(Math.abs(this.endX - x) < threshold && this.position.y < y) {
|
|
253
|
+
else if( Math.abs(this.endX - x) < threshold && this.position.y < y ) {
|
|
191
254
|
this.dragging = 'end';
|
|
192
255
|
canvas.style.cursor = "grabbing";
|
|
193
256
|
}
|
|
194
257
|
else {
|
|
195
258
|
this.dragging = 'current';
|
|
196
259
|
canvas.style.cursor = "grabbing";
|
|
197
|
-
|
|
198
|
-
if(x < this.startX) {
|
|
260
|
+
|
|
261
|
+
if( x < this.startX ) {
|
|
199
262
|
this.currentX = this.startX;
|
|
200
263
|
}
|
|
201
|
-
else if(x > this.endX) {
|
|
264
|
+
else if( x > this.endX ) {
|
|
202
265
|
this.currentX = this.endX;
|
|
203
266
|
}
|
|
204
267
|
else {
|
|
205
268
|
this.currentX = x;
|
|
206
269
|
}
|
|
207
270
|
|
|
208
|
-
|
|
209
|
-
this.onChangeCurrent(this.currentX);
|
|
210
|
-
}
|
|
271
|
+
this.onSetCurrentValue( this.currentX );
|
|
211
272
|
}
|
|
212
273
|
|
|
213
274
|
this._draw();
|
|
214
275
|
}
|
|
215
276
|
|
|
216
|
-
onMouseUp
|
|
277
|
+
onMouseUp( e ) {
|
|
278
|
+
|
|
279
|
+
if( this.onMouse ) {
|
|
280
|
+
this.onMouse( e );
|
|
281
|
+
}
|
|
282
|
+
|
|
217
283
|
e.preventDefault();
|
|
218
284
|
|
|
219
285
|
this.dragging = false;
|
|
220
286
|
this.hovering = false;
|
|
221
287
|
|
|
222
|
-
if(!this.canvas) {
|
|
288
|
+
if( !this.canvas || e.cancelBubble ) {
|
|
223
289
|
return;
|
|
224
290
|
}
|
|
225
291
|
|
|
@@ -227,8 +293,13 @@ class TimeBar {
|
|
|
227
293
|
canvas.style.cursor = "default";
|
|
228
294
|
}
|
|
229
295
|
|
|
230
|
-
onMouseMove
|
|
231
|
-
|
|
296
|
+
onMouseMove( e ) {
|
|
297
|
+
|
|
298
|
+
if( this.onMouse ) {
|
|
299
|
+
this.onMouse( e );
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if( !this.canvas || e.cancelBubble ) {
|
|
232
303
|
return;
|
|
233
304
|
}
|
|
234
305
|
|
|
@@ -239,43 +310,38 @@ class TimeBar {
|
|
|
239
310
|
const x = e.target == canvas ? e.offsetX : e.clientX - canvas.offsetLeft;
|
|
240
311
|
const y = e.target == canvas ? e.offsetY : e.clientY - canvas.offsetTop;
|
|
241
312
|
|
|
242
|
-
if(this.dragging) {
|
|
243
|
-
switch(this.dragging) {
|
|
313
|
+
if( this.dragging ) {
|
|
314
|
+
switch( this.dragging ) {
|
|
244
315
|
case 'start':
|
|
245
|
-
this.startX = Math.max(this.position.x, Math.min(this.endX, x));
|
|
316
|
+
this.startX = Math.max(this.position.x, Math.min(this.endX, x));
|
|
246
317
|
this.currentX = this.startX;
|
|
247
|
-
|
|
248
|
-
this.onChangeStart(this.startX);
|
|
249
|
-
}
|
|
318
|
+
this.onSetStartValue(this.startX);
|
|
250
319
|
break;
|
|
251
320
|
case 'end':
|
|
252
|
-
this.endX = Math.max(this.startX, Math.min(this.position.x + this.lineWidth, x));
|
|
321
|
+
this.endX = Math.max( this.startX, Math.min(this.position.x + this.lineWidth, x) );
|
|
253
322
|
this.currentX = this.endX;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
323
|
+
|
|
324
|
+
this.onSetEndValue( this.endX );
|
|
257
325
|
break;
|
|
258
326
|
default:
|
|
259
|
-
this.currentX = Math.max(this.startX, Math.min(this.endX, x));
|
|
327
|
+
this.currentX = Math.max( this.startX, Math.min(this.endX, x) );
|
|
260
328
|
break;
|
|
261
329
|
}
|
|
262
330
|
|
|
263
|
-
|
|
264
|
-
this.onChangeCurrent(this.currentX);
|
|
265
|
-
}
|
|
331
|
+
this.onSetCurrentValue( this.currentX );
|
|
266
332
|
}
|
|
267
333
|
else {
|
|
268
334
|
const threshold = this.markerWidth * 0.5;
|
|
269
335
|
|
|
270
|
-
if(Math.abs(this.startX - x) < threshold ) {
|
|
336
|
+
if( Math.abs(this.startX - x) < threshold ) {
|
|
271
337
|
this.hovering = 'start';
|
|
272
338
|
canvas.style.cursor = "grab";
|
|
273
339
|
}
|
|
274
|
-
else if(Math.abs(this.endX - x) < threshold) {
|
|
340
|
+
else if( Math.abs(this.endX - x) < threshold ) {
|
|
275
341
|
this.hovering = 'end';
|
|
276
342
|
canvas.style.cursor = "grab";
|
|
277
343
|
}
|
|
278
|
-
else if(Math.abs(this.currentX - x) < threshold) {
|
|
344
|
+
else if( Math.abs(this.currentX - x) < threshold ) {
|
|
279
345
|
this.hovering = 'current';
|
|
280
346
|
canvas.style.cursor = "grab";
|
|
281
347
|
}
|
|
@@ -287,16 +353,16 @@ class TimeBar {
|
|
|
287
353
|
this._draw();
|
|
288
354
|
}
|
|
289
355
|
|
|
290
|
-
resize
|
|
356
|
+
resize( size ) {
|
|
291
357
|
this.canvas.width = size[0];
|
|
292
358
|
this.canvas.height = size[1];
|
|
293
359
|
|
|
294
360
|
let newWidth = size[0] - this.offset * 2;
|
|
295
|
-
newWidth = newWidth < 0.00001 ? 0.00001 : newWidth; // actual width of the line = canvas.width - offsetleft - offsetRight
|
|
361
|
+
newWidth = newWidth < 0.00001 ? 0.00001 : newWidth; // actual width of the line = canvas.width - offsetleft - offsetRight
|
|
296
362
|
const startRatio = (this.startX - this.offset) / this.lineWidth;
|
|
297
363
|
const currentRatio = (this.currentX - this.offset) / this.lineWidth;
|
|
298
364
|
const endRatio = (this.endX - this.offset) / this.lineWidth;
|
|
299
|
-
|
|
365
|
+
|
|
300
366
|
this.lineWidth = newWidth;
|
|
301
367
|
this.startX = Math.min( Math.max(newWidth * startRatio, 0), newWidth ) + this.offset;
|
|
302
368
|
this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset;
|
|
@@ -305,6 +371,7 @@ class TimeBar {
|
|
|
305
371
|
this._draw();
|
|
306
372
|
}
|
|
307
373
|
}
|
|
374
|
+
|
|
308
375
|
LX.TimeBar = TimeBar;
|
|
309
376
|
|
|
310
377
|
|
|
@@ -318,16 +385,17 @@ class VideoEditor {
|
|
|
318
385
|
|
|
319
386
|
this.playing = false;
|
|
320
387
|
this.requestId = null;
|
|
321
|
-
|
|
388
|
+
this.videoReady = false;
|
|
322
389
|
this.currentTime = this.startTime = 0;
|
|
323
390
|
this.startTimeString = "0:0";
|
|
324
391
|
this.endTimeString = "0:0";
|
|
325
|
-
|
|
392
|
+
this._updateTime = true;
|
|
393
|
+
this.speed = options.speed || 1;
|
|
326
394
|
this.mainArea = area;
|
|
327
395
|
|
|
328
396
|
let videoArea = null;
|
|
329
397
|
let controlsArea = null;
|
|
330
|
-
if(options.controlsArea) {
|
|
398
|
+
if( options.controlsArea ) {
|
|
331
399
|
videoArea = area;
|
|
332
400
|
controlsArea = options.controlsArea;
|
|
333
401
|
}
|
|
@@ -335,7 +403,7 @@ class VideoEditor {
|
|
|
335
403
|
[videoArea, controlsArea] = area.split({ type: 'vertical', sizes: ["85%", null], minimizable: false, resize: false });
|
|
336
404
|
}
|
|
337
405
|
controlsArea.root.classList.add('lexconstrolsarea');
|
|
338
|
-
|
|
406
|
+
|
|
339
407
|
this.cropArea = document.createElement("div");
|
|
340
408
|
this.cropArea.id = "cropArea";
|
|
341
409
|
this.cropArea.className = "resize-area hidden"
|
|
@@ -343,20 +411,20 @@ class VideoEditor {
|
|
|
343
411
|
this.brCrop = document.createElement("div");
|
|
344
412
|
this.brCrop.className = " resize-handle br"; // bottom right
|
|
345
413
|
this.cropArea.append(this.brCrop);
|
|
346
|
-
|
|
414
|
+
|
|
347
415
|
this.crop = options.crop;
|
|
348
416
|
this.dragOffsetX = 0;
|
|
349
417
|
this.dragOffsetY = 0;
|
|
350
418
|
// Create video element and load it
|
|
351
419
|
let video = this.video = options.video ?? document.createElement( 'video' );
|
|
352
|
-
this.
|
|
353
|
-
|
|
354
|
-
if(options.src) {
|
|
420
|
+
this.loop = options.loop ?? false;
|
|
421
|
+
|
|
422
|
+
if( options.src ) {
|
|
355
423
|
this.video.src = options.src;
|
|
356
|
-
this.
|
|
424
|
+
this.loadVideo( options );
|
|
357
425
|
}
|
|
358
|
-
|
|
359
|
-
if(options.videoArea) {
|
|
426
|
+
|
|
427
|
+
if( options.videoArea ) {
|
|
360
428
|
options.videoArea.root.classList.add("lexvideoeditor");
|
|
361
429
|
options.videoArea.attach(this.cropArea);
|
|
362
430
|
videoArea.attach(options.videoArea);
|
|
@@ -397,7 +465,10 @@ class VideoEditor {
|
|
|
397
465
|
this.controlsPanelLeft.addButton('', "", (v) => {
|
|
398
466
|
this.playing = !this.playing;
|
|
399
467
|
if(this.playing) {
|
|
400
|
-
this.video.
|
|
468
|
+
if( this.video.currentTime + 0.000001 >= this.endTime) {
|
|
469
|
+
this.video.currentTime = this.startTime;
|
|
470
|
+
}
|
|
471
|
+
this.video.play()
|
|
401
472
|
}
|
|
402
473
|
else {
|
|
403
474
|
this.video.pause();
|
|
@@ -405,7 +476,38 @@ class VideoEditor {
|
|
|
405
476
|
this.controlsPanelLeft.refresh();
|
|
406
477
|
}, { width: '40px', icon: (this.playing ? 'Pause@solid' : 'Play@solid'), className: "justify-center"});
|
|
407
478
|
|
|
408
|
-
|
|
479
|
+
if(this.speedDialog) {
|
|
480
|
+
this.speedDialog.close();
|
|
481
|
+
}
|
|
482
|
+
this.speedDialog = null;
|
|
483
|
+
const btn = this.controlsPanelLeft.addButton('', '', (v, e) => {
|
|
484
|
+
|
|
485
|
+
// if(this.speedDialog) {
|
|
486
|
+
// this.speedDialog.close();
|
|
487
|
+
// this.speedDialog = null;
|
|
488
|
+
// return;
|
|
489
|
+
// }
|
|
490
|
+
|
|
491
|
+
const panel = new LX.Panel();
|
|
492
|
+
panel.addNumber("Speed", this.speed, (v) => {
|
|
493
|
+
this.speed = v;
|
|
494
|
+
this.video.playbackRate = v;
|
|
495
|
+
if( this.onChangeSpeed ) {
|
|
496
|
+
this.onChangeSpeed( v );
|
|
497
|
+
}
|
|
498
|
+
}, {min: 0, max: 2.5, step: 0.01, nameWidth: "50px"})
|
|
499
|
+
|
|
500
|
+
new LX.Popover( e.target, [ panel ], { align: "start", side: "top", sideOffset: 12 } );
|
|
501
|
+
|
|
502
|
+
}, { width: '40px', title: 'speed', icon: "Timer@solid", className: "justify-center" } );
|
|
503
|
+
|
|
504
|
+
this.controlsPanelLeft.addButton('', 'Loop', (v) => {
|
|
505
|
+
this.loop = !this.loop;
|
|
506
|
+
|
|
507
|
+
this.controlsPanelLeft.refresh();
|
|
508
|
+
}, { width: '40px', title: 'loop', icon: ( 'Repeat@solid'), className: `justify-center`, buttonClass: `${(this.loop ? 'bg-accent' : '')}`});
|
|
509
|
+
|
|
510
|
+
this.controlsPanelLeft.addLabel(this.startTimeString, {width: "100px"});
|
|
409
511
|
this.controlsPanelLeft.endLine();
|
|
410
512
|
|
|
411
513
|
let availableWidth = leftArea.root.clientWidth - controlsLeft.root.clientWidth;
|
|
@@ -414,7 +516,8 @@ class VideoEditor {
|
|
|
414
516
|
|
|
415
517
|
this.controlsPanelLeft.refresh();
|
|
416
518
|
controlsLeft.root.style.minWidth = 'fit-content';
|
|
417
|
-
|
|
519
|
+
// controlsLeft.root.classList.add();
|
|
520
|
+
controlsLeft.attach(this.controlsPanelLeft);
|
|
418
521
|
|
|
419
522
|
// Create right controls panel (ens time)
|
|
420
523
|
this.controlsPanelRight = new LX.Panel({className: 'lexcontrolspanel'});
|
|
@@ -426,15 +529,15 @@ class VideoEditor {
|
|
|
426
529
|
controlsRight.root.style.minWidth = 'fit-content';
|
|
427
530
|
controlsRight.attach(this.controlsPanelRight);
|
|
428
531
|
|
|
429
|
-
this.timebar.onChangeCurrent = this.
|
|
430
|
-
this.timebar.onChangeStart = this.
|
|
431
|
-
this.timebar.onChangeEnd = this.
|
|
532
|
+
this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
|
|
533
|
+
this.timebar.onChangeStart = this._setStartTime.bind(this);
|
|
534
|
+
this.timebar.onChangeEnd = this._setEndTime.bind(this);
|
|
432
535
|
|
|
433
536
|
window.addEventListener('resize', (v) => {
|
|
434
537
|
if(this.onResize) {
|
|
435
538
|
this.onResize([videoArea.root.clientWidth, videoArea.root.clientHeight]);
|
|
436
539
|
}
|
|
437
|
-
bottomArea.setSize([
|
|
540
|
+
bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
|
|
438
541
|
let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth - controlsRight.root.clientWidth;
|
|
439
542
|
this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
|
|
440
543
|
this.dragCropArea( { clientX: -1, clientY: -1 } );
|
|
@@ -443,17 +546,20 @@ class VideoEditor {
|
|
|
443
546
|
})
|
|
444
547
|
|
|
445
548
|
this.onKeyUp = (event) => {
|
|
446
|
-
if(this.controls && event.key == " ") {
|
|
549
|
+
if( this.controls && event.key == " " ) {
|
|
447
550
|
event.preventDefault();
|
|
448
551
|
event.stopPropagation();
|
|
449
552
|
|
|
450
|
-
|
|
553
|
+
this.playing = !this.playing;
|
|
554
|
+
if( this.playing ) {
|
|
555
|
+
if( this.video.currentTime + 0.000001 >= this.endTime) {
|
|
556
|
+
this.video.currentTime = this.startTime;
|
|
557
|
+
}
|
|
451
558
|
this.video.play();
|
|
452
559
|
}
|
|
453
560
|
else {
|
|
454
561
|
this.video.pause();
|
|
455
562
|
}
|
|
456
|
-
this.playing = !this.playing;
|
|
457
563
|
this.controlsPanelLeft.refresh();
|
|
458
564
|
}
|
|
459
565
|
}
|
|
@@ -461,15 +567,18 @@ class VideoEditor {
|
|
|
461
567
|
window.addEventListener( "keyup", this.onKeyUp);
|
|
462
568
|
|
|
463
569
|
videoArea.onresize = (v) => {
|
|
464
|
-
bottomArea.
|
|
570
|
+
if( bottomArea.parentArea ) {
|
|
571
|
+
bottomArea.setSize([bottomArea.parentArea.root.clientWidth, 40]);
|
|
572
|
+
}
|
|
465
573
|
|
|
466
574
|
const ratio = this.video.clientHeight / this.video.videoHeight;
|
|
467
575
|
this.cropArea.style.height = this.video.clientHeight + "px";
|
|
468
576
|
this.cropArea.style.width = this.video.videoWidth * ratio + "px";
|
|
469
577
|
}
|
|
470
578
|
|
|
579
|
+
|
|
471
580
|
timeBarArea.onresize = (v) => {
|
|
472
|
-
let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth - controlsRight.root.clientWidth;
|
|
581
|
+
let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth - controlsRight.root.clientWidth - 20;
|
|
473
582
|
this.timebar.resize([availableWidth, v.height]);
|
|
474
583
|
}
|
|
475
584
|
|
|
@@ -477,14 +586,14 @@ class VideoEditor {
|
|
|
477
586
|
|
|
478
587
|
// Add canvas event listeneres
|
|
479
588
|
parent.addEventListener( "mousedown", (event) => {
|
|
480
|
-
if(this.controls) {
|
|
481
|
-
|
|
482
|
-
}
|
|
589
|
+
// if(this.controls) {
|
|
590
|
+
// this.timebar.onMouseDown(event);
|
|
591
|
+
// }
|
|
483
592
|
});
|
|
484
|
-
parent.addEventListener( "mouseup",
|
|
485
|
-
if(this.controls) {
|
|
486
|
-
|
|
487
|
-
}
|
|
593
|
+
parent.addEventListener( "mouseup", (event) => {
|
|
594
|
+
// if(this.controls) {
|
|
595
|
+
// this.timebar.onMouseUp(event);
|
|
596
|
+
// }
|
|
488
597
|
|
|
489
598
|
if( ( this.isDragging || this.isResizing ) && this.onCropArea ) {
|
|
490
599
|
if( this.onCropArea ) {
|
|
@@ -496,23 +605,23 @@ class VideoEditor {
|
|
|
496
605
|
|
|
497
606
|
});
|
|
498
607
|
parent.addEventListener( "mousemove", (event) => {
|
|
499
|
-
if(this.controls) {
|
|
500
|
-
|
|
501
|
-
}
|
|
608
|
+
// if(this.controls) {
|
|
609
|
+
// this.timebar.onMouseMove(event);
|
|
610
|
+
// }
|
|
502
611
|
|
|
503
|
-
if (this.isResizing) {
|
|
504
|
-
this.resizeCropArea(event);
|
|
612
|
+
if ( this.isResizing ) {
|
|
613
|
+
this.resizeCropArea( event );
|
|
505
614
|
}
|
|
506
615
|
|
|
507
|
-
if(this.isDragging) {
|
|
508
|
-
this.dragCropArea(event);
|
|
616
|
+
if( this.isDragging ) {
|
|
617
|
+
this.dragCropArea( event );
|
|
509
618
|
}
|
|
510
619
|
});
|
|
511
620
|
|
|
512
|
-
this.cropArea.addEventListener(
|
|
621
|
+
this.cropArea.addEventListener( "mousedown", (event) => {
|
|
513
622
|
|
|
514
|
-
|
|
515
|
-
if (event.target === this.cropArea) {
|
|
623
|
+
|
|
624
|
+
if ( event.target === this.cropArea ) {
|
|
516
625
|
const rect = this.cropArea.getBoundingClientRect();
|
|
517
626
|
this.isDragging = true;
|
|
518
627
|
|
|
@@ -521,9 +630,9 @@ class VideoEditor {
|
|
|
521
630
|
}
|
|
522
631
|
});
|
|
523
632
|
|
|
524
|
-
document.querySelectorAll(
|
|
633
|
+
document.querySelectorAll(".resize-handle" ).forEach( handle => {
|
|
525
634
|
|
|
526
|
-
handle.addEventListener(
|
|
635
|
+
handle.addEventListener("mousedown", ( e ) => {
|
|
527
636
|
|
|
528
637
|
e.stopPropagation();
|
|
529
638
|
if (handle.classList[1] === 'br') {
|
|
@@ -531,23 +640,24 @@ class VideoEditor {
|
|
|
531
640
|
}
|
|
532
641
|
});
|
|
533
642
|
});
|
|
534
|
-
|
|
643
|
+
|
|
535
644
|
this.onChangeStart = null;
|
|
536
645
|
this.onChangeEnd = null;
|
|
537
646
|
}
|
|
538
647
|
|
|
539
|
-
resizeCropArea(event) {
|
|
648
|
+
resizeCropArea( event ) {
|
|
540
649
|
|
|
541
650
|
const mouseX = event.clientX;
|
|
542
651
|
const mouseY = event.clientY;
|
|
543
|
-
|
|
652
|
+
|
|
544
653
|
const isCropHidden = this.cropArea.classList.contains("hidden");
|
|
545
654
|
const nodes = this.cropArea.parentElement.childNodes;
|
|
546
|
-
|
|
655
|
+
|
|
547
656
|
const rectCrop = this.cropArea.getBoundingClientRect();
|
|
548
657
|
const rectVideo = this.video.getBoundingClientRect();
|
|
549
658
|
let width = Math.max( 0, Math.min( mouseX - rectCrop.left, rectVideo.width ) );
|
|
550
659
|
let height = Math.max( 0, Math.min( mouseY - rectCrop.top, rectVideo.height ) );
|
|
660
|
+
|
|
551
661
|
if ( (rectCrop.left + width) > rectVideo.right ){
|
|
552
662
|
width = Math.min( rectVideo.width, rectVideo.right - rectCrop.left);
|
|
553
663
|
}
|
|
@@ -555,9 +665,9 @@ class VideoEditor {
|
|
|
555
665
|
height = Math.min( rectVideo.height, rectVideo.bottom - rectCrop.top);
|
|
556
666
|
}
|
|
557
667
|
|
|
558
|
-
if ( !isCropHidden ){
|
|
668
|
+
if ( !isCropHidden ){
|
|
559
669
|
for( let i = 0; i < nodes.length; i++ ) {
|
|
560
|
-
if( nodes[i] != this.cropArea ) {
|
|
670
|
+
if( nodes[i] != this.cropArea ) {
|
|
561
671
|
const rectEl = nodes[i].getBoundingClientRect();
|
|
562
672
|
nodes[i].style.webkitMask = `linear-gradient(#000 0 0) ${rectCrop.x - rectEl.left}px ${ rectCrop.y - rectEl.top }px / ${width}px ${height}px, linear-gradient(rgba(0, 0, 0, 0.3) 0 0)`;
|
|
563
673
|
nodes[i].style.webkitMaskRepeat = 'no-repeat';
|
|
@@ -587,13 +697,13 @@ class VideoEditor {
|
|
|
587
697
|
if( y < rectVideo.top ) {
|
|
588
698
|
y = rectVideo.top;
|
|
589
699
|
}
|
|
590
|
-
|
|
700
|
+
|
|
591
701
|
if( y + rectCrop.height > rectVideo.bottom ) {
|
|
592
702
|
y = Math.max( rectVideo.top, rectVideo.bottom - rectCrop.height );
|
|
593
703
|
}
|
|
594
704
|
|
|
595
705
|
if ( !this.cropArea.classList.contains("hidden") ){
|
|
596
|
-
const nodes = this.cropArea.parentElement.childNodes;
|
|
706
|
+
const nodes = this.cropArea.parentElement.childNodes;
|
|
597
707
|
for( let i = 0; i < nodes.length; i++ ) {
|
|
598
708
|
if( nodes[i] != this.cropArea ) {
|
|
599
709
|
const rectEl = nodes[i].getBoundingClientRect();
|
|
@@ -609,29 +719,59 @@ class VideoEditor {
|
|
|
609
719
|
|
|
610
720
|
}
|
|
611
721
|
|
|
612
|
-
async
|
|
613
|
-
|
|
722
|
+
async loadVideo( options = {} ) {
|
|
723
|
+
this.videoReady = false;
|
|
724
|
+
while( this.video.duration === Infinity || isNaN(this.video.duration) || !this.timebar ) {
|
|
614
725
|
await new Promise(r => setTimeout(r, 1000));
|
|
615
726
|
this.video.currentTime = 10000000 * Math.random();
|
|
616
727
|
}
|
|
617
|
-
|
|
728
|
+
this.video.currentTime = 0.01; // BUG: some videos will not play unless this line is present
|
|
729
|
+
|
|
730
|
+
// Duration can change if the video is dynamic (stream). This function is to ensure to load all buffer data
|
|
731
|
+
const forceLoadChunks = () => {
|
|
732
|
+
const state = this.videoReady;
|
|
733
|
+
if( this.video.readyState > 3 ) {
|
|
734
|
+
this.videoReady = true;
|
|
735
|
+
}
|
|
736
|
+
if( !state ) {
|
|
737
|
+
this.video.currentTime = this.video.duration;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this.video.addEventListener( "canplaythrough", forceLoadChunks, { passive: true } );
|
|
742
|
+
|
|
743
|
+
this.video.ondurationchange = ( v ) => {
|
|
744
|
+
if( this.video.duration != this.endTime ) {
|
|
745
|
+
|
|
746
|
+
this.video.currentTime = this.startTime;
|
|
747
|
+
console.log("duration changed from", this.endTime, " to ", this.video.duration);
|
|
748
|
+
this.endTime = this.video.duration;
|
|
749
|
+
this.timebar.setDuration( this.endTime );
|
|
750
|
+
this.timebar.setEndTime( this.endTime );
|
|
751
|
+
}
|
|
752
|
+
this.video.currentTime = this.startTime;
|
|
753
|
+
this.timebar.setCurrentTime( this.video.currentTime );
|
|
754
|
+
|
|
755
|
+
}
|
|
756
|
+
|
|
618
757
|
this.timebar.startX = this.timebar.position.x;
|
|
619
758
|
this.timebar.endX = this.timebar.position.x + this.timebar.lineWidth;
|
|
620
|
-
|
|
621
|
-
this.video.currentTime = 0.01; // BUG: some videos will not play unless this line is present
|
|
759
|
+
this.startTime = 0;
|
|
622
760
|
this.endTime = this.video.duration;
|
|
623
|
-
|
|
624
|
-
this.
|
|
625
|
-
this.
|
|
626
|
-
this.timebar.
|
|
627
|
-
this.
|
|
628
|
-
this.timebar.
|
|
629
|
-
|
|
761
|
+
this.timebar.setDuration( this.endTime );
|
|
762
|
+
this.timebar.setEndTime( this.video.duration );
|
|
763
|
+
this.timebar.setStartTime( this.startTime );
|
|
764
|
+
this.timebar.setCurrentTime( this.startTime );
|
|
765
|
+
//this.timebar.setStartValue(this.timebar.startX);
|
|
766
|
+
//this.timebar.currentX = this._timeToX(this.video.currentTime);
|
|
767
|
+
//this.timebar.setCurrentValue(this.timebar.currentX);
|
|
768
|
+
//this.timebar.update( this.timebar.currentX );
|
|
769
|
+
|
|
630
770
|
if ( !this.requestId ){ // only have one update on flight
|
|
631
771
|
this._update();
|
|
632
|
-
}
|
|
772
|
+
}
|
|
633
773
|
this.controls = options.controls ?? true;
|
|
634
|
-
|
|
774
|
+
|
|
635
775
|
if ( !this.controls ) {
|
|
636
776
|
this.hideControls();
|
|
637
777
|
}
|
|
@@ -643,49 +783,44 @@ class VideoEditor {
|
|
|
643
783
|
|
|
644
784
|
if( this.crop ) {
|
|
645
785
|
this.showCropArea();
|
|
646
|
-
}
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
647
788
|
this.hideCropArea();
|
|
648
789
|
}
|
|
649
790
|
|
|
650
791
|
window.addEventListener( "keyup", this.onKeyUp);
|
|
651
792
|
|
|
652
793
|
if( this.onVideoLoaded ) {
|
|
653
|
-
this.onVideoLoaded(this.video);
|
|
794
|
+
this.onVideoLoaded( this.video );
|
|
654
795
|
}
|
|
655
796
|
}
|
|
656
797
|
|
|
657
798
|
_update () {
|
|
658
799
|
|
|
659
|
-
if(this.onDraw) {
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
if(this.playing) {
|
|
663
|
-
if(this.video.currentTime >= this.endTime) {
|
|
664
|
-
this.video.
|
|
800
|
+
// if( this.onDraw ) {
|
|
801
|
+
// this.onDraw();
|
|
802
|
+
// }
|
|
803
|
+
if( this.playing ) {
|
|
804
|
+
if( this.video.currentTime + 0.000001 >= this.endTime ) {
|
|
805
|
+
this.video.pause();
|
|
806
|
+
if( !this.loop ) {
|
|
807
|
+
this.playing = false;
|
|
808
|
+
this.controlsPanelLeft.refresh();
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
this.video.currentTime = this.startTime;
|
|
812
|
+
this.video.play();
|
|
813
|
+
}
|
|
665
814
|
}
|
|
666
|
-
|
|
667
|
-
this.
|
|
668
|
-
this.
|
|
815
|
+
this._updateTime = false;
|
|
816
|
+
this.timebar.setCurrentTime( this.video.currentTime );
|
|
817
|
+
this._updateTime = true;
|
|
669
818
|
}
|
|
670
819
|
|
|
671
|
-
this.requestId = requestAnimationFrame(this._update.bind(this));
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
_xToTime (x) {
|
|
675
|
-
return ((x - this.timebar.offset) / (this.timebar.lineWidth)) * this.video.duration;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
_timeToX (time) {
|
|
679
|
-
return (time / this.video.duration) * (this.timebar.lineWidth) + this.timebar.offset;
|
|
820
|
+
this.requestId = requestAnimationFrame( this._update.bind(this) );
|
|
680
821
|
}
|
|
681
822
|
|
|
682
|
-
|
|
683
|
-
const t = this._xToTime(x);
|
|
684
|
-
|
|
685
|
-
if(updateTime) {
|
|
686
|
-
this.video.currentTime = t;
|
|
687
|
-
}
|
|
688
|
-
//console.log( "Computed: " + t)
|
|
823
|
+
timeToString( t ) {
|
|
689
824
|
let mzminutes = Math.floor(t / 60);
|
|
690
825
|
let mzseconds = Math.floor(t - (mzminutes * 60));
|
|
691
826
|
let mzmiliseconds = Math.floor((t - mzseconds)*100);
|
|
@@ -693,56 +828,51 @@ class VideoEditor {
|
|
|
693
828
|
mzmiliseconds = mzmiliseconds < 10 ? ('0' + mzmiliseconds) : mzmiliseconds;
|
|
694
829
|
mzseconds = mzseconds < 10 ? ('0' + mzseconds) : mzseconds;
|
|
695
830
|
mzminutes = mzminutes < 10 ? ('0' + mzminutes) : mzminutes;
|
|
696
|
-
|
|
831
|
+
return mzminutes + ':' + mzseconds + '.' + mzmiliseconds;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
_setCurrentTime( t ) {
|
|
835
|
+
|
|
836
|
+
if( this.video.currentTime != t && this._updateTime ) {
|
|
837
|
+
this.video.currentTime = t;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
this.currentTimeString = this.timeToString( t );
|
|
697
841
|
this.controlsCurrentPanel.refresh();
|
|
698
842
|
|
|
699
|
-
if(this.onSetTime) {
|
|
843
|
+
if( this.onSetTime ) {
|
|
700
844
|
this.onSetTime(t);
|
|
701
845
|
}
|
|
846
|
+
if( this.onChangeCurrent ) {
|
|
847
|
+
this.onChangeCurrent( t );
|
|
848
|
+
}
|
|
702
849
|
}
|
|
703
850
|
|
|
704
|
-
|
|
705
|
-
const t = this._xToTime(x);
|
|
851
|
+
_setStartTime( t ) {
|
|
706
852
|
this.startTime = this.video.currentTime = t;
|
|
707
853
|
|
|
708
|
-
|
|
709
|
-
let mzseconds = Math.floor(t - (mzminutes * 60));
|
|
710
|
-
let mzmiliseconds = Math.floor((t - mzseconds)*100);
|
|
711
|
-
|
|
712
|
-
mzmiliseconds = mzmiliseconds < 10 ? ('0' + mzmiliseconds) : mzmiliseconds;
|
|
713
|
-
mzseconds = mzseconds < 10 ? ('0' + mzseconds) : mzseconds;
|
|
714
|
-
mzminutes = mzminutes < 10 ? ('0' + mzminutes) : mzminutes;
|
|
715
|
-
this.startTimeString = mzminutes+':'+mzseconds+'.'+mzmiliseconds;
|
|
854
|
+
this.startTimeString = this.timeToString( t );
|
|
716
855
|
this.controlsPanelLeft.refresh();
|
|
717
|
-
|
|
718
|
-
|
|
856
|
+
|
|
857
|
+
if( this.onSetTime ) {
|
|
858
|
+
this.onSetTime( t );
|
|
719
859
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
this.onChangeStart(t);
|
|
860
|
+
if( this.onChangeStart ) {
|
|
861
|
+
this.onChangeStart ( t );
|
|
723
862
|
}
|
|
724
863
|
}
|
|
725
864
|
|
|
726
|
-
|
|
727
|
-
const t = this._xToTime(x);
|
|
865
|
+
_setEndTime( t ) {
|
|
728
866
|
this.endTime = this.video.currentTime = t;
|
|
729
867
|
|
|
730
|
-
|
|
731
|
-
let mzseconds = Math.floor(t - (mzminutes * 60));
|
|
732
|
-
let mzmiliseconds = Math.floor((t - mzseconds)*100);
|
|
733
|
-
|
|
734
|
-
mzmiliseconds = mzmiliseconds < 10 ? ('0' + mzmiliseconds) : mzmiliseconds;
|
|
735
|
-
mzseconds = mzseconds < 10 ? ('0' + mzseconds) : mzseconds;
|
|
736
|
-
mzminutes = mzminutes < 10 ? ('0' + mzminutes) : mzminutes;
|
|
737
|
-
|
|
738
|
-
this.endTimeString = mzminutes+':'+mzseconds+'.'+mzmiliseconds;
|
|
868
|
+
this.endTimeString = this.timeToString( t)
|
|
739
869
|
this.controlsPanelRight.refresh();
|
|
740
|
-
|
|
870
|
+
|
|
871
|
+
if( this.onSetTime ) {
|
|
741
872
|
this.onSetTime(t);
|
|
742
873
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
this.onChangeEnd(t);
|
|
874
|
+
if( this.onChangeEnd ) {
|
|
875
|
+
this.onChangeEnd( t );
|
|
746
876
|
}
|
|
747
877
|
}
|
|
748
878
|
|
|
@@ -781,7 +911,7 @@ class VideoEditor {
|
|
|
781
911
|
|
|
782
912
|
const nodes = this.cropArea.parentElement.childNodes;
|
|
783
913
|
for( let i = 0; i < nodes.length; i++ ) {
|
|
784
|
-
if( nodes[i] != this.cropArea ) {
|
|
914
|
+
if( nodes[i] != this.cropArea ) {
|
|
785
915
|
nodes[i].style.webkitMask = "";
|
|
786
916
|
nodes[i].style.webkitMaskRepeat = 'no-repeat';
|
|
787
917
|
}
|
|
@@ -808,7 +938,7 @@ class VideoEditor {
|
|
|
808
938
|
|
|
809
939
|
unbind ( ) {
|
|
810
940
|
this.stopUpdates();
|
|
811
|
-
|
|
941
|
+
|
|
812
942
|
this.video.pause();
|
|
813
943
|
this.playing = false;
|
|
814
944
|
this.controlsPanelLeft.refresh();
|