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.
@@ -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 + 8);
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
- _draw() {
64
- const ctx = this.ctx;
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 + 4;
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 (x) {
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 (e) {
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
- if(this.onChangeCurrent) {
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 (e) {
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 (e) {
231
- if(!this.canvas) {
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
- if(this.onChangeStart) {
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
- if(this.onChangeEnd) {
255
- this.onChangeEnd(this.endX);
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
- if(this.onChangeCurrent) {
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 (size) {
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.video.loop = true;
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._loadVideo(options);
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.play();
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
- this.controlsPanelLeft.addLabel(this.startTimeString, {width: 100});
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
- controlsLeft.root.classList.add(); controlsLeft.attach(this.controlsPanelLeft);
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._setCurrentValue.bind(this);
430
- this.timebar.onChangeStart = this._setStartValue.bind(this);
431
- this.timebar.onChangeEnd = this._setEndValue.bind(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([videoArea.root.clientWidth, 40]);
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
- if(!this.playing) {
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.setSize([v.width, 40]);
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
- this.timebar.onMouseDown(event);
482
- }
589
+ // if(this.controls) {
590
+ // this.timebar.onMouseDown(event);
591
+ // }
483
592
  });
484
- parent.addEventListener( "mouseup", (event) => {
485
- if(this.controls) {
486
- this.timebar.onMouseUp(event);
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
- this.timebar.onMouseMove(event);
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('mousedown', (event) => {
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('.resize-handle').forEach(handle => {
633
+ document.querySelectorAll(".resize-handle" ).forEach( handle => {
525
634
 
526
- handle.addEventListener('mousedown', (e) => {
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 _loadVideo( options = {} ) {
613
- while(this.video.duration === Infinity || isNaN(this.video.duration) || !this.timebar) {
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._setEndValue(this.timebar.endX);
625
- this._setStartValue(this.timebar.startX);
626
- this.timebar.currentX = this._timeToX(this.video.currentTime);
627
- this._setCurrentValue(this.timebar.currentX, false);
628
- this.timebar.update(this.timebar.currentX);
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
- }else{
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
- this.onDraw();
661
- }
662
- if(this.playing) {
663
- if(this.video.currentTime >= this.endTime) {
664
- this.video.currentTime = this.startTime;
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
- const x = this._timeToX(this.video.currentTime);
667
- this._setCurrentValue(x, false);
668
- this.timebar.update(x);
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
- _setCurrentValue ( x, updateTime = true ) {
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
- this.currentTimeString = mzminutes+':'+mzseconds+'.'+mzmiliseconds;
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
- _setStartValue ( x ) {
705
- const t = this._xToTime(x);
851
+ _setStartTime( t ) {
706
852
  this.startTime = this.video.currentTime = t;
707
853
 
708
- let mzminutes = Math.floor(t / 60);
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
- if(this.onSetTime) {
718
- this.onSetTime(t);
856
+
857
+ if( this.onSetTime ) {
858
+ this.onSetTime( t );
719
859
  }
720
-
721
- if(this.onChangeStart) {
722
- this.onChangeStart(t);
860
+ if( this.onChangeStart ) {
861
+ this.onChangeStart ( t );
723
862
  }
724
863
  }
725
864
 
726
- _setEndValue ( x ) {
727
- const t = this._xToTime(x);
865
+ _setEndTime( t ) {
728
866
  this.endTime = this.video.currentTime = t;
729
867
 
730
- let mzminutes = Math.floor(t / 60);
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
- if(this.onSetTime) {
870
+
871
+ if( this.onSetTime ) {
741
872
  this.onSetTime(t);
742
873
  }
743
-
744
- if(this.onChangeEnd) {
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();