@financial-times/cp-content-pipeline-ui 6.15.2 → 6.15.3

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.
@@ -458,6 +458,7 @@ class Clip extends ClipInterface {
458
458
  // disable default controls
459
459
  video.controls = false
460
460
 
461
+ // CREATE ALL THE CUSTOM CONTOLS
461
462
  const customControls = document.createElement('div')
462
463
  customControls.classList.add('cp-clip__video-controls')
463
464
 
@@ -600,12 +601,7 @@ class Clip extends ClipInterface {
600
601
  customControls.prepend(playIconContainer)
601
602
  customControls.append(bottomBar)
602
603
 
603
- video.addEventListener('loadeddata', () => {
604
- const hasNoAudio = this.videoHasNoAudio()
605
- if (hasNoAudio) {
606
- this.muteIcon?.replaceWith(noAudioIcon)
607
- }
608
- })
604
+ // END CREATE ALL THE CUSTOM CONTOLS
609
605
 
610
606
  video.addEventListener('playing', () => {
611
607
  //resize controls in case poster is different size than video at the end
@@ -653,20 +649,9 @@ class Clip extends ClipInterface {
653
649
  }
654
650
  })
655
651
 
656
- video.addEventListener('waiting', () => {
657
- this.showLoadingIndicator()
658
- })
659
-
660
- video.addEventListener('canplay', () => {
661
- this.hideLoadingIndicator()
662
- })
663
-
664
- video.addEventListener('canplaythrough', () => {
665
- this.hideLoadingIndicator()
666
- })
667
-
668
652
  this.resizeControls()
669
653
 
654
+ // EVENTS TO DO WITH READY STATE / LOADING STATUS
670
655
  const onLoadedMetaData = () => {
671
656
  this.resizeControls()
672
657
  const duration = formatTime(this.getDuration())
@@ -677,12 +662,50 @@ class Clip extends ClipInterface {
677
662
  this.containerEl.classList.add('cp-clip--ready')
678
663
  }
679
664
 
665
+ const onLoadedData = () => {
666
+ const hasNoAudio = this.videoHasNoAudio()
667
+ if (hasNoAudio) {
668
+ this.muteIcon?.replaceWith(noAudioIcon)
669
+ }
670
+ }
671
+
680
672
  if (video.readyState >= 1) {
681
673
  onLoadedMetaData()
682
674
  } else {
683
675
  video.addEventListener('loadedmetadata', onLoadedMetaData)
684
676
  }
685
677
 
678
+ if (video.readyState >= 2) {
679
+ onLoadedData()
680
+ } else {
681
+ video.addEventListener('loadeddata', () => {
682
+ onLoadedData()
683
+ })
684
+ }
685
+
686
+ if (video.readyState >= 3) {
687
+ this.hideLoadingIndicator()
688
+ }
689
+
690
+ // NB: videos can go back and forth between readystates 2 and 3
691
+
692
+ // will fire when video drops from readystate >=3 to readystate <=2
693
+ video.addEventListener('waiting', () => {
694
+ this.showLoadingIndicator()
695
+ })
696
+
697
+ // will fire when video goes from readystate <=2 to 3
698
+ video.addEventListener('canplay', () => {
699
+ this.hideLoadingIndicator()
700
+ })
701
+
702
+ // will fire when video gets to readystate 4
703
+ video.addEventListener('canplaythrough', () => {
704
+ this.hideLoadingIndicator()
705
+ })
706
+
707
+ // END EVENTS TO DO WITH READY STATE / LOADING STATUS
708
+
686
709
  customControls.addEventListener('click', (event) => {
687
710
  const isVisible = isElementVisible(customControls)
688
711
  this.fadeIn()
@@ -33,6 +33,27 @@ export const inlineVideoAutoplay = Clip({
33
33
  ],
34
34
  },
35
35
  })
36
+ export const inlineVideoNoAudio = Clip({
37
+ dataLayout: 'in-line',
38
+ url: 'https://d1e00ek4ebabms.cloudfront.net/production/84d7e1b0-e8b2-4ffc-a798-306f29dc9d52.png',
39
+ description:
40
+ ' Dolor sit amet, consectetur adipiscing elit. Sed sit amet odio quis ante auctor dapibus. Sed dapibus cursus nisi, tincidunt sagittis sapien vehicula vitae.',
41
+ poster:
42
+ 'https://www.ft.com/__origami/service/image/v2/images/raw/https%3A%2F%2Fd1e00ek4ebabms.cloudfront.net%2Fproduction%2F84d7e1b0-e8b2-4ffc-a798-306f29dc9d52.png?fit=scale-down&source=next&width=700',
43
+ caption: 'Aenean lobortis volutpat nunc vitae elementum',
44
+ autoplay: true,
45
+ muted: true,
46
+ loop: true,
47
+ noAudio: true,
48
+ credits: 'Copyright Line',
49
+ accessibility: {
50
+ captions: [
51
+ {
52
+ url: 'https://next-media-api.ft.com/captions/16862228218010.vtt',
53
+ },
54
+ ],
55
+ },
56
+ })
36
57
 
37
58
  export const teaserClipWithoutExpander = Clip({
38
59
  dataLayout: 'in-line',
@@ -4,6 +4,7 @@
4
4
  import {
5
5
  inlineVideo,
6
6
  inlineVideoAutoplay,
7
+ inlineVideoNoAudio,
7
8
  teaserClipWithMultipleSources,
8
9
  teaserClipWithoutExpander,
9
10
  } from './fixtures'
@@ -33,6 +34,9 @@ initialMocks()
33
34
 
34
35
  const mockVideo = (video) => {
35
36
  let playInterval = null
37
+ video.dispatchEvent(new Event('loadeddata'))
38
+ video.dispatchEvent(new Event('canplay'))
39
+ video.dispatchEvent(new Event('canplaythrough'))
36
40
  video.load = () => {
37
41
  /* do nothing */
38
42
  }
@@ -43,7 +47,6 @@ const mockVideo = (video) => {
43
47
  //mock that the video has a duration of 10 seconds when we play it due it is fully loaded
44
48
  video.duration = 10000
45
49
  video.paused = false
46
- video.dispatchEvent(new Event('loadeddata'))
47
50
  video.dispatchEvent(new Event('playing'))
48
51
  }
49
52
  video.pause = () => {
@@ -413,6 +416,52 @@ describe('Clip', () => {
413
416
  })
414
417
  })
415
418
 
419
+ describe('with no audio', () => {
420
+ let clips: Array<Clip>
421
+
422
+ beforeEach(() => {
423
+ document.body.innerHTML = inlineVideoNoAudio
424
+ clips = Clip.init()
425
+ mockClips(clips)
426
+ })
427
+
428
+ afterEach(() => {
429
+ const elem = document.body.children[0]
430
+ document.body.removeChild(elem)
431
+ clips.forEach((clip) => clip.destroy())
432
+ })
433
+
434
+ it("if the video doesn't have audio it should show 'no audio' icon after playing", (done) => {
435
+ const clip = clips[0]
436
+ expect(clip.muted).toBe(true)
437
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
438
+ const listener = (e) => {
439
+ expect(clip.muted).toBe(true)
440
+ expect(
441
+ clip.containerEl.querySelector('.cp-clip__no-audio')
442
+ ).toBeTruthy()
443
+ clip.videoEl.removeEventListener('playing', listener)
444
+ done()
445
+ }
446
+ clip.videoEl.addEventListener('playing', listener)
447
+ clip.videoEl.play()
448
+ })
449
+ it("if the video doesn't have audio it shouldn't show mute icon after playing", (done) => {
450
+ const clip = clips[0]
451
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
452
+ const listener = (e) => {
453
+ expect(clip.muted).toBe(true)
454
+ expect(
455
+ clip.containerEl.querySelector('[data-test-id="cp-clip__mute-icon"]')
456
+ ).toBeFalsy()
457
+ clip.videoEl.removeEventListener('playing', listener)
458
+ done()
459
+ }
460
+ clip.videoEl.addEventListener('playing', listener)
461
+ clip.videoEl.play()
462
+ })
463
+ })
464
+
416
465
  describe('with autoplay', () => {
417
466
  let clips: Array<Clip>
418
467
 
@@ -584,23 +633,6 @@ describe('Clip', () => {
584
633
  clip.videoEl.play()
585
634
  })
586
635
 
587
- it("if the video doesn't have audio it shouldn't show mute icon after playing", (done) => {
588
- const clip = clips[0]
589
- clip.opts.noAudio = true
590
- expect(clip.muted).toBe(true)
591
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
592
- const listener = (e) => {
593
- expect(clip.muted).toBe(true)
594
- expect(
595
- clip.containerEl.querySelector('[data-test-id="cp-clip__mute-icon"]')
596
- ).toBeFalsy()
597
- clip.videoEl.removeEventListener('playing', listener)
598
- done()
599
- }
600
- clip.videoEl.addEventListener('playing', listener)
601
- clip.videoEl.play()
602
- })
603
-
604
636
  it('mute icon should change and set or unset the video muted', (done) => {
605
637
  const clip = clips[0]
606
638
  expect(clip.muted).toBe(true)
@@ -627,23 +659,6 @@ describe('Clip', () => {
627
659
  clip.muteIcon.click()
628
660
  })
629
661
 
630
- it("if the video doesn't have audio it should show no audio icon after playing", (done) => {
631
- const clip = clips[0]
632
- clip.opts.noAudio = true
633
- expect(clip.muted).toBe(true)
634
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
635
- const listener = (e) => {
636
- expect(clip.muted).toBe(true)
637
- expect(
638
- clip.containerEl.querySelector('.cp-clip__no-audio')
639
- ).toBeTruthy()
640
- clip.videoEl.removeEventListener('playing', listener)
641
- done()
642
- }
643
- clip.videoEl.addEventListener('playing', listener)
644
- clip.videoEl.play()
645
- })
646
-
647
662
  it('should assing empty string to the poster attribute if autoplay mode is enabled', () => {
648
663
  const clip = clips[0]
649
664
  expect(clip.videoEl.getAttribute('poster')).toBe('')