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