aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-08-05 09:44:58 +0200
committerChocobozzz <chocobozzz@cpy.re>2020-08-07 08:58:29 +0200
commit4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4 (patch)
tree2c1aa81a536b50d6da0181aba6fce1db972f6191 /client/src/assets/player
parent5abc96fca2496f33075796db208fccc3543e0f65 (diff)
downloadPeerTube-4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4.tar.gz
PeerTube-4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4.tar.zst
PeerTube-4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4.zip
Handle basic playlist in embed
Diffstat (limited to 'client/src/assets/player')
-rw-r--r--client/src/assets/player/images/tick-white.svg5
-rw-r--r--client/src/assets/player/peertube-player-manager.ts18
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts25
-rw-r--r--client/src/assets/player/playlist/playlist-button.ts61
-rw-r--r--client/src/assets/player/playlist/playlist-menu-item.ts98
-rw-r--r--client/src/assets/player/playlist/playlist-menu.ts124
-rw-r--r--client/src/assets/player/playlist/playlist-plugin.ts35
7 files changed, 360 insertions, 6 deletions
diff --git a/client/src/assets/player/images/tick-white.svg b/client/src/assets/player/images/tick-white.svg
index d329e6bfb..8868a2481 100644
--- a/client/src/assets/player/images/tick-white.svg
+++ b/client/src/assets/player/images/tick-white.svg
@@ -1,8 +1,7 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 2<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <defs></defs> 3 <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
4 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round"> 4 <g transform="translate(-356.000000, -115.000000)" stroke="#fff" stroke-width="2">
5 <g id="Artboard-4" transform="translate(-356.000000, -115.000000)" stroke="#fff" stroke-width="2">
6 <g id="8" transform="translate(356.000000, 115.000000)"> 5 <g id="8" transform="translate(356.000000, 115.000000)">
7 <path d="M21,6 L9,18" id="Path-14"></path> 6 <path d="M21,6 L9,18" id="Path-14"></path>
8 <path d="M9,13 L4,18" id="Path-14" transform="translate(6.500000, 15.500000) scale(-1, 1) translate(-6.500000, -15.500000) "></path> 7 <path d="M9,13 L4,18" id="Path-14" transform="translate(6.500000, 15.500000) scale(-1, 1) translate(-6.500000, -15.500000) "></path>
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 6a6d63462..dcfa3a593 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -18,14 +18,21 @@ import './videojs-components/settings-menu-item'
18import './videojs-components/settings-panel' 18import './videojs-components/settings-panel'
19import './videojs-components/settings-panel-child' 19import './videojs-components/settings-panel-child'
20import './videojs-components/theater-button' 20import './videojs-components/theater-button'
21import './playlist/playlist-plugin'
21import videojs from 'video.js' 22import videojs from 'video.js'
22import { VideoFile } from '@shared/models'
23import { isDefaultLocale } from '@shared/core-utils/i18n' 23import { isDefaultLocale } from '@shared/core-utils/i18n'
24import { VideoFile } from '@shared/models'
24import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' 25import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
25import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' 26import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
26import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' 27import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
27import { getStoredP2PEnabled } from './peertube-player-local-storage' 28import { getStoredP2PEnabled } from './peertube-player-local-storage'
28import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions } from './peertube-videojs-typings' 29import {
30 P2PMediaLoaderPluginOptions,
31 PlaylistPluginOptions,
32 UserWatching,
33 VideoJSCaption,
34 VideoJSPluginOptions
35} from './peertube-videojs-typings'
29import { TranslationsManager } from './translations-manager' 36import { TranslationsManager } from './translations-manager'
30import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils' 37import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils'
31 38
@@ -71,6 +78,9 @@ export interface CommonOptions extends CustomizationOptions {
71 78
72 autoplay: boolean 79 autoplay: boolean
73 nextVideo?: Function 80 nextVideo?: Function
81
82 playlist?: PlaylistPluginOptions
83
74 videoDuration: number 84 videoDuration: number
75 enableHotkeys: boolean 85 enableHotkeys: boolean
76 inactivityTimeout: number 86 inactivityTimeout: number
@@ -203,6 +213,10 @@ export class PeertubePlayerManager {
203 } 213 }
204 } 214 }
205 215
216 if (commonOptions.playlist) {
217 plugins.playlist = commonOptions.playlist
218 }
219
206 if (commonOptions.enableHotkeys === true) { 220 if (commonOptions.enableHotkeys === true) {
207 PeertubePlayerManager.addHotkeysOptions(plugins) 221 PeertubePlayerManager.addHotkeysOptions(plugins)
208 } 222 }
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index 1506a04ac..b72c4b0f9 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -1,10 +1,11 @@
1import { Config, Level } from 'hls.js' 1import { Config, Level } from 'hls.js'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { VideoFile } from '@shared/models' 3import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models'
4import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin' 4import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin'
5import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' 5import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
6import { PlayerMode } from './peertube-player-manager' 6import { PlayerMode } from './peertube-player-manager'
7import { PeerTubePlugin } from './peertube-plugin' 7import { PeerTubePlugin } from './peertube-plugin'
8import { PlaylistPlugin } from './playlist/playlist-plugin'
8import { EndCardOptions } from './upnext/end-card' 9import { EndCardOptions } from './upnext/end-card'
9import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin' 10import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
10 11
@@ -45,6 +46,8 @@ declare module 'video.js' {
45 dock (options: { title: string, description: string }): void 46 dock (options: { title: string, description: string }): void
46 47
47 upnext (options: Partial<EndCardOptions>): void 48 upnext (options: Partial<EndCardOptions>): void
49
50 playlist (): PlaylistPlugin
48 } 51 }
49} 52}
50 53
@@ -105,6 +108,16 @@ type PeerTubePluginOptions = {
105 stopTime: number | string 108 stopTime: number | string
106} 109}
107 110
111type PlaylistPluginOptions = {
112 elements: VideoPlaylistElement[]
113
114 playlist: VideoPlaylist
115
116 getCurrentPosition: () => number
117
118 onItemClicked: (element: VideoPlaylistElement) => void
119}
120
108type WebtorrentPluginOptions = { 121type WebtorrentPluginOptions = {
109 playerElement: HTMLVideoElement 122 playerElement: HTMLVideoElement
110 123
@@ -125,6 +138,8 @@ type P2PMediaLoaderPluginOptions = {
125} 138}
126 139
127type VideoJSPluginOptions = { 140type VideoJSPluginOptions = {
141 playlist?: PlaylistPluginOptions
142
128 peertube: PeerTubePluginOptions 143 peertube: PeerTubePluginOptions
129 144
130 webtorrent?: WebtorrentPluginOptions 145 webtorrent?: WebtorrentPluginOptions
@@ -170,10 +185,18 @@ type PlayerNetworkInfo = {
170 } 185 }
171} 186}
172 187
188type PlaylistItemOptions = {
189 element: VideoPlaylistElement
190
191 onClicked: Function
192}
193
173export { 194export {
174 PlayerNetworkInfo, 195 PlayerNetworkInfo,
196 PlaylistItemOptions,
175 ResolutionUpdateData, 197 ResolutionUpdateData,
176 AutoResolutionUpdateData, 198 AutoResolutionUpdateData,
199 PlaylistPluginOptions,
177 VideoJSCaption, 200 VideoJSCaption,
178 UserWatching, 201 UserWatching,
179 PeerTubePluginOptions, 202 PeerTubePluginOptions,
diff --git a/client/src/assets/player/playlist/playlist-button.ts b/client/src/assets/player/playlist/playlist-button.ts
new file mode 100644
index 000000000..a7996ec60
--- /dev/null
+++ b/client/src/assets/player/playlist/playlist-button.ts
@@ -0,0 +1,61 @@
1import videojs from 'video.js'
2import { PlaylistPluginOptions } from '../peertube-videojs-typings'
3import { PlaylistMenu } from './playlist-menu'
4
5const ClickableComponent = videojs.getComponent('ClickableComponent')
6
7class PlaylistButton extends ClickableComponent {
8 private playlistInfoElement: HTMLElement
9 private wrapper: HTMLElement
10
11 constructor (player: videojs.Player, options?: PlaylistPluginOptions & { playlistMenu: PlaylistMenu }) {
12 super(player, options as any)
13 }
14
15 createEl () {
16 this.wrapper = super.createEl('div', {
17 className: 'vjs-playlist-button',
18 innerHTML: '',
19 tabIndex: -1
20 }) as HTMLElement
21
22 const icon = super.createEl('div', {
23 className: 'vjs-playlist-icon',
24 innerHTML: '',
25 tabIndex: -1
26 })
27
28 this.playlistInfoElement = super.createEl('div', {
29 className: 'vjs-playlist-info',
30 innerHTML: '',
31 tabIndex: -1
32 }) as HTMLElement
33
34 this.wrapper.appendChild(icon)
35 this.wrapper.appendChild(this.playlistInfoElement)
36
37 this.update()
38
39 return this.wrapper
40 }
41
42 update () {
43 const options = this.options_ as PlaylistPluginOptions
44
45 this.playlistInfoElement.innerHTML = options.getCurrentPosition() + '/' + options.playlist.videosLength
46 this.wrapper.title = this.player().localize('Playlist: {1}', [ options.playlist.displayName ])
47 }
48
49 handleClick () {
50 const playlistMenu = this.getPlaylistMenu()
51 playlistMenu.open()
52 }
53
54 private getPlaylistMenu () {
55 return (this.options_ as any).playlistMenu as PlaylistMenu
56 }
57}
58
59videojs.registerComponent('PlaylistButton', PlaylistButton)
60
61export { PlaylistButton }
diff --git a/client/src/assets/player/playlist/playlist-menu-item.ts b/client/src/assets/player/playlist/playlist-menu-item.ts
new file mode 100644
index 000000000..916c6338f
--- /dev/null
+++ b/client/src/assets/player/playlist/playlist-menu-item.ts
@@ -0,0 +1,98 @@
1import videojs from 'video.js'
2import { VideoPlaylistElement } from '@shared/models'
3import { PlaylistItemOptions } from '../peertube-videojs-typings'
4
5const Component = videojs.getComponent('Component')
6
7class PlaylistMenuItem extends Component {
8 private element: VideoPlaylistElement
9
10 constructor (player: videojs.Player, options?: PlaylistItemOptions) {
11 super(player, options as any)
12
13 this.emitTapEvents()
14
15 this.element = options.element
16
17 this.on([ 'click', 'tap' ], () => this.switchPlaylistItem())
18 this.on('keydown', event => this.handleKeyDown(event))
19 }
20
21 createEl () {
22 const options = this.options_ as PlaylistItemOptions
23
24 const li = super.createEl('li', {
25 className: 'vjs-playlist-menu-item',
26 innerHTML: ''
27 }) as HTMLElement
28
29 const positionBlock = super.createEl('div', {
30 className: 'item-position-block'
31 })
32
33 const position = super.createEl('div', {
34 className: 'item-position',
35 innerHTML: options.element.position
36 })
37
38 const player = super.createEl('div', {
39 className: 'item-player'
40 })
41
42 positionBlock.appendChild(position)
43 positionBlock.appendChild(player)
44
45 li.appendChild(positionBlock)
46
47 const thumbnail = super.createEl('img', {
48 src: window.location.origin + options.element.video.thumbnailPath
49 })
50
51 const infoBlock = super.createEl('div', {
52 className: 'info-block'
53 })
54
55 const title = super.createEl('div', {
56 innerHTML: options.element.video.name,
57 className: 'title'
58 })
59
60 const channel = super.createEl('div', {
61 innerHTML: options.element.video.channel.displayName,
62 className: 'channel'
63 })
64
65 infoBlock.appendChild(title)
66 infoBlock.appendChild(channel)
67
68 li.append(thumbnail)
69 li.append(infoBlock)
70
71 return li
72 }
73
74 setSelected (selected: boolean) {
75 if (selected) this.addClass('vjs-selected')
76 else this.removeClass('vjs-selected')
77 }
78
79 getElement () {
80 return this.element
81 }
82
83 private handleKeyDown (event: KeyboardEvent) {
84 if (event.code === 'Space' || event.code === 'Enter') {
85 this.switchPlaylistItem()
86 }
87 }
88
89 private switchPlaylistItem () {
90 const options = this.options_ as PlaylistItemOptions
91
92 options.onClicked()
93 }
94}
95
96Component.registerComponent('PlaylistMenuItem', PlaylistMenuItem)
97
98export { PlaylistMenuItem }
diff --git a/client/src/assets/player/playlist/playlist-menu.ts b/client/src/assets/player/playlist/playlist-menu.ts
new file mode 100644
index 000000000..7d7d9e12f
--- /dev/null
+++ b/client/src/assets/player/playlist/playlist-menu.ts
@@ -0,0 +1,124 @@
1import videojs from 'video.js'
2import { VideoPlaylistElement } from '@shared/models'
3import { PlaylistPluginOptions } from '../peertube-videojs-typings'
4import { PlaylistMenuItem } from './playlist-menu-item'
5
6const Component = videojs.getComponent('Component')
7
8class PlaylistMenu extends Component {
9 private menuItems: PlaylistMenuItem[]
10
11 constructor (player: videojs.Player, options?: PlaylistPluginOptions) {
12 super(player, options as any)
13
14 this.player().on('userinactive', () => {
15 this.close()
16 })
17
18 this.player().on('click', event => {
19 let current = event.target as HTMLElement
20
21 do {
22 if (
23 current.classList.contains('vjs-playlist-menu') ||
24 current.classList.contains('vjs-playlist-button')
25 ) {
26 return
27 }
28
29 current = current.parentElement
30 } while (current)
31
32 this.close()
33 })
34 }
35
36 createEl () {
37 this.menuItems = []
38
39 const options = this.getOptions()
40
41 const menu = super.createEl('div', {
42 className: 'vjs-playlist-menu',
43 innerHTML: '',
44 tabIndex: -1
45 })
46
47 const header = super.createEl('div', {
48 className: 'header'
49 })
50
51 const headerLeft = super.createEl('div')
52
53 const leftTitle = super.createEl('div', {
54 innerHTML: options.playlist.displayName,
55 className: 'title'
56 })
57
58 const leftSubtitle = super.createEl('div', {
59 innerHTML: this.player().localize('By {1}', [ options.playlist.videoChannel.displayName ]),
60 className: 'channel'
61 })
62
63 headerLeft.appendChild(leftTitle)
64 headerLeft.appendChild(leftSubtitle)
65
66 const tick = super.createEl('div', {
67 className: 'cross'
68 })
69 tick.addEventListener('click', () => this.close())
70
71 header.appendChild(headerLeft)
72 header.appendChild(tick)
73
74 const list = super.createEl('ol')
75
76 for (const playlistElement of options.elements) {
77 const item = new PlaylistMenuItem(this.player(), {
78 element: playlistElement,
79 onClicked: () => this.onItemClicked(playlistElement)
80 })
81
82 list.appendChild(item.el())
83
84 this.menuItems.push(item)
85 }
86
87 menu.appendChild(header)
88 menu.appendChild(list)
89
90 return menu
91 }
92
93 update () {
94 const options = this.getOptions()
95
96 this.updateSelected(options.getCurrentPosition())
97 }
98
99 open () {
100 this.player().addClass('playlist-menu-displayed')
101 }
102
103 close () {
104 this.player().removeClass('playlist-menu-displayed')
105 }
106
107 updateSelected (newPosition: number) {
108 for (const item of this.menuItems) {
109 item.setSelected(item.getElement().position === newPosition)
110 }
111 }
112
113 private getOptions () {
114 return this.options_ as PlaylistPluginOptions
115 }
116
117 private onItemClicked (element: VideoPlaylistElement) {
118 this.getOptions().onItemClicked(element)
119 }
120}
121
122Component.registerComponent('PlaylistMenu', PlaylistMenu)
123
124export { PlaylistMenu }
diff --git a/client/src/assets/player/playlist/playlist-plugin.ts b/client/src/assets/player/playlist/playlist-plugin.ts
new file mode 100644
index 000000000..b69d82e3c
--- /dev/null
+++ b/client/src/assets/player/playlist/playlist-plugin.ts
@@ -0,0 +1,35 @@
1import videojs from 'video.js'
2import { PlaylistPluginOptions } from '../peertube-videojs-typings'
3import { PlaylistButton } from './playlist-button'
4import { PlaylistMenu } from './playlist-menu'
5
6const Plugin = videojs.getPlugin('plugin')
7
8class PlaylistPlugin extends Plugin {
9 private playlistMenu: PlaylistMenu
10 private playlistButton: PlaylistButton
11 private options: PlaylistPluginOptions
12
13 constructor (player: videojs.Player, options?: PlaylistPluginOptions) {
14 super(player, options)
15
16 this.options = options
17
18 this.player.ready(() => {
19 player.addClass('vjs-playlist')
20 })
21
22 this.playlistMenu = new PlaylistMenu(player, options)
23 this.playlistButton = new PlaylistButton(player, Object.assign({}, options, { playlistMenu: this.playlistMenu }))
24
25 player.addChild(this.playlistMenu, options)
26 player.addChild(this.playlistButton, options)
27 }
28
29 updateSelected () {
30 this.playlistMenu.updateSelected(this.options.getCurrentPosition())
31 }
32}
33
34videojs.registerPlugin('playlist', PlaylistPlugin)
35export { PlaylistPlugin }