]>
Commit | Line | Data |
---|---|---|
1 | import videojs from 'video.js' | |
2 | ||
3 | function getMainTemplate (options: any) { | |
4 | return ` | |
5 | <div class="vjs-upnext-top"> | |
6 | <span class="vjs-upnext-headtext">${options.headText}</span> | |
7 | <div class="vjs-upnext-title"></div> | |
8 | </div> | |
9 | <div class="vjs-upnext-autoplay-icon"> | |
10 | <svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%"> | |
11 | <circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle> | |
12 | <circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" | |
13 | stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)" | |
14 | ></circle> | |
15 | <polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg> | |
16 | </div> | |
17 | <span class="vjs-upnext-bottom"> | |
18 | <span class="vjs-upnext-cancel"> | |
19 | <button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button> | |
20 | </span> | |
21 | <span class="vjs-upnext-suspended">${options.suspendedText}</span> | |
22 | </span> | |
23 | ` | |
24 | } | |
25 | ||
26 | export interface EndCardOptions extends videojs.ComponentOptions { | |
27 | next: () => void | |
28 | getTitle: () => string | |
29 | timeout: number | |
30 | cancelText: string | |
31 | headText: string | |
32 | suspendedText: string | |
33 | condition: () => boolean | |
34 | suspended: () => boolean | |
35 | } | |
36 | ||
37 | const Component = videojs.getComponent('Component') | |
38 | class EndCard extends Component { | |
39 | options_: EndCardOptions | |
40 | ||
41 | dashOffsetTotal = 586 | |
42 | dashOffsetStart = 293 | |
43 | interval = 50 | |
44 | upNextEvents = new videojs.EventTarget() | |
45 | ticks = 0 | |
46 | totalTicks: number | |
47 | ||
48 | container: HTMLDivElement | |
49 | title: HTMLElement | |
50 | autoplayRing: HTMLElement | |
51 | cancelButton: HTMLElement | |
52 | suspendedMessage: HTMLElement | |
53 | nextButton: HTMLElement | |
54 | ||
55 | constructor (player: videojs.Player, options: EndCardOptions) { | |
56 | super(player, options) | |
57 | ||
58 | this.totalTicks = this.options_.timeout / this.interval | |
59 | ||
60 | player.on('ended', (_: any) => { | |
61 | if (!this.options_.condition()) return | |
62 | ||
63 | player.addClass('vjs-upnext--showing') | |
64 | this.showCard((canceled: boolean) => { | |
65 | player.removeClass('vjs-upnext--showing') | |
66 | this.container.style.display = 'none' | |
67 | if (!canceled) { | |
68 | this.options_.next() | |
69 | } | |
70 | }) | |
71 | }) | |
72 | ||
73 | player.on('playing', () => { | |
74 | this.upNextEvents.trigger('playing') | |
75 | }) | |
76 | } | |
77 | ||
78 | createEl () { | |
79 | const container = super.createEl('div', { | |
80 | className: 'vjs-upnext-content', | |
81 | innerHTML: getMainTemplate(this.options_) | |
82 | }) as HTMLDivElement | |
83 | ||
84 | this.container = container | |
85 | container.style.display = 'none' | |
86 | ||
87 | this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0] as HTMLElement | |
88 | this.title = container.getElementsByClassName('vjs-upnext-title')[0] as HTMLElement | |
89 | this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0] as HTMLElement | |
90 | this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0] as HTMLElement | |
91 | this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0] as HTMLElement | |
92 | ||
93 | this.cancelButton.onclick = () => { | |
94 | this.upNextEvents.trigger('cancel') | |
95 | } | |
96 | ||
97 | this.nextButton.onclick = () => { | |
98 | this.upNextEvents.trigger('next') | |
99 | } | |
100 | ||
101 | return container | |
102 | } | |
103 | ||
104 | showCard (cb: (value: boolean) => void) { | |
105 | let timeout: any | |
106 | ||
107 | this.autoplayRing.setAttribute('stroke-dasharray', `${this.dashOffsetStart}`) | |
108 | this.autoplayRing.setAttribute('stroke-dashoffset', `${-this.dashOffsetStart}`) | |
109 | ||
110 | this.title.innerHTML = this.options_.getTitle() | |
111 | ||
112 | this.upNextEvents.one('cancel', () => { | |
113 | clearTimeout(timeout) | |
114 | cb(true) | |
115 | }) | |
116 | ||
117 | this.upNextEvents.one('playing', () => { | |
118 | clearTimeout(timeout) | |
119 | cb(true) | |
120 | }) | |
121 | ||
122 | this.upNextEvents.one('next', () => { | |
123 | clearTimeout(timeout) | |
124 | cb(false) | |
125 | }) | |
126 | ||
127 | const goToPercent = (percent: number) => { | |
128 | const newOffset = Math.max(-this.dashOffsetTotal, -this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100) | |
129 | this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset) | |
130 | } | |
131 | ||
132 | const tick = () => { | |
133 | goToPercent((this.ticks++) * 100 / this.totalTicks) | |
134 | } | |
135 | ||
136 | const update = () => { | |
137 | if (this.options_.suspended()) { | |
138 | this.suspendedMessage.innerText = this.options_.suspendedText | |
139 | goToPercent(0) | |
140 | this.ticks = 0 | |
141 | timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer | |
142 | } else if (this.ticks >= this.totalTicks) { | |
143 | clearTimeout(timeout) | |
144 | cb(false) | |
145 | } else { | |
146 | this.suspendedMessage.innerText = '' | |
147 | tick() | |
148 | timeout = setTimeout(update.bind(this), this.interval) | |
149 | } | |
150 | } | |
151 | ||
152 | this.container.style.display = 'block' | |
153 | timeout = setTimeout(update.bind(this), this.interval) | |
154 | } | |
155 | } | |
156 | ||
157 | videojs.registerComponent('EndCard', EndCard) |