aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
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
parent5abc96fca2496f33075796db208fccc3543e0f65 (diff)
downloadPeerTube-4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4.tar.gz
PeerTube-4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4.tar.zst
PeerTube-4572c3d0d92f5b1b79b34dbe2c7b6557a8a5b7e4.zip
Handle basic playlist in embed
Diffstat (limited to 'client')
-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
-rw-r--r--client/src/sass/include/_miniature.scss13
-rw-r--r--client/src/sass/include/_mixins.scss15
-rw-r--r--client/src/sass/player/index.scss3
-rw-r--r--client/src/sass/player/playlist.scss165
-rw-r--r--client/src/standalone/videos/embed.ts29
12 files changed, 567 insertions, 24 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 }
diff --git a/client/src/sass/include/_miniature.scss b/client/src/sass/include/_miniature.scss
index 976bbf4d6..97b4c690b 100644
--- a/client/src/sass/include/_miniature.scss
+++ b/client/src/sass/include/_miniature.scss
@@ -52,18 +52,7 @@ $play-overlay-width: 18px;
52 } 52 }
53 53
54 .icon { 54 .icon {
55 width: 0; 55 @include play-icon($play-overlay-height, $play-overlay-width);
56 height: 0;
57
58 position: absolute;
59 left: 50%;
60 top: 50%;
61 transform: translate(-50%, -50%) scale(0.5);
62
63 border-top: ($play-overlay-height / 2) solid transparent;
64 border-bottom: ($play-overlay-height / 2) solid transparent;
65
66 border-left: $play-overlay-width solid rgba(255, 255, 255, 0.95);
67 } 56 }
68 } 57 }
69 58
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index ee2fe0497..e4c2dffa0 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -1019,3 +1019,18 @@
1019 } 1019 }
1020 } 1020 }
1021} 1021}
1022
1023@mixin play-icon ($width, $height) {
1024 width: 0;
1025 height: 0;
1026
1027 position: absolute;
1028 left: 50%;
1029 top: 50%;
1030 transform: translate(-50%, -50%) scale(0.5);
1031
1032 border-top: ($height / 2) solid transparent;
1033 border-bottom: ($height / 2) solid transparent;
1034
1035 border-left: $width solid rgba(255, 255, 255, 0.95);
1036}
diff --git a/client/src/sass/player/index.scss b/client/src/sass/player/index.scss
index 58ce3ac96..fe92ce5e0 100644
--- a/client/src/sass/player/index.scss
+++ b/client/src/sass/player/index.scss
@@ -4,4 +4,5 @@
4@import './settings-menu'; 4@import './settings-menu';
5@import './spinner'; 5@import './spinner';
6@import './upnext'; 6@import './upnext';
7@import './bezels.scss'; \ No newline at end of file 7@import './bezels.scss';
8@import './playlist.scss';
diff --git a/client/src/sass/player/playlist.scss b/client/src/sass/player/playlist.scss
new file mode 100644
index 000000000..c242acba8
--- /dev/null
+++ b/client/src/sass/player/playlist.scss
@@ -0,0 +1,165 @@
1$playlist-menu-width: 350px;
2
3.vjs-playlist-menu {
4 position: absolute;
5 right: 0;
6 height: 100%;
7 width: $playlist-menu-width;
8 background: rgba(0, 0, 0, 0.8);
9 z-index: 101;
10 transition: right 0.2s;
11
12 // Hidden
13 right: -$playlist-menu-width;
14
15 ol {
16 padding: 0;
17 margin: 0;
18 }
19
20 .header {
21 border-bottom: 1px solid $header-border-color;
22 padding: 20px 10px;
23 display: flex;
24 justify-content: space-between;
25
26 .title {
27 font-size: 14px;
28 margin-bottom: 5px;
29 white-space: nowrap;
30 text-overflow: ellipsis;
31 }
32
33 .channel {
34 font-size: 11px;
35 color: #bfbfbf;
36 white-space: nowrap;
37 text-overflow: ellipsis;
38 }
39
40 .cross {
41 cursor: pointer;
42 width: 20px;
43 height: 20px;
44 mask-image: url('#{$assets-path}/images/feather/x.svg');
45 -webkit-mask-image: url('#{$assets-path}/images/feather/x.svg');
46 background-color: white;
47 mask-size: cover;
48 -webkit-mask-size: cover;
49 }
50 }
51}
52
53.playlist-menu-displayed {
54
55 .vjs-playlist-menu {
56 right: 0;
57 display: block;
58 }
59
60 .vjs-playlist-button {
61 display: none;
62 }
63}
64
65@media screen and (max-width: $playlist-menu-width) {
66 .vjs-playlist-menu {
67 width: 100%;
68 min-width: unset;
69 display: none;
70 }
71
72 .playlist-menu-displayed .vjs-playlist-menu {
73 display: block;
74 }
75}
76
77.vjs-playlist-button {
78 font-size: 15px;
79 position: absolute;
80 right: 0;
81 top: 0;
82 z-index: 100;
83 padding: 1em;
84 cursor: pointer;
85}
86
87.vjs-playlist-icon {
88 width: 22px;
89 height: 22px;
90 mask-image: url('#{$assets-path}/images/feather/list.svg');
91 -webkit-mask-image: url('#{$assets-path}/images/feather/list.svg');
92 background-color: white;
93 mask-size: cover;
94 -webkit-mask-size: cover;
95 margin-bottom: 3px;
96}
97
98.vjs-playing.vjs-user-inactive .vjs-playlist-button {
99 opacity: 0;
100
101 transition: opacity 1s;
102}
103
104.vjs-playing.vjs-no-flex.vjs-user-inactive .vjs-playlist-button {
105 display: none;
106}
107
108.vjs-playlist-menu-item {
109 cursor: pointer;
110 display: flex;
111 padding: 10px 0;
112
113 .item-position-block {
114 position: relative;
115 display: flex;
116 align-items: center;
117 justify-content: center;
118 width: 30px;
119 }
120
121 .item-player {
122 display: none;
123
124 @include play-icon(20px, 16px);
125 }
126
127 &.vjs-selected {
128 background-color: rgba(150, 150, 150, 0.3);
129
130 .item-position {
131 display: none;
132 }
133
134 .item-player {
135 display: block;
136 }
137 }
138
139 &:hover {
140 background-color: rgba(150, 150, 150, 0.2);
141 }
142
143 img {
144 width: 80px;
145 height: 40px;
146 }
147
148 .info-block {
149 margin-left: 10px;
150
151 .title {
152 font-size: 13px;
153 margin-bottom: 5px;
154 white-space: nowrap;
155 text-overflow: ellipsis;
156 }
157
158 .channel {
159 font-size: 11px;
160 color: #bfbfbf;
161 white-space: nowrap;
162 text-overflow: ellipsis;
163 }
164 }
165}
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 71bd04e76..17b0ee9ef 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -324,7 +324,11 @@ export class PeerTubeEmbed {
324 324
325 this.currentPlaylistElement = next 325 this.currentPlaylistElement = next
326 326
327 const res = await this.loadVideo(this.currentPlaylistElement.video.uuid) 327 return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
328 }
329
330 private async loadVideoAndBuildPlayer (uuid: string) {
331 const res = await this.loadVideo(uuid)
328 if (res === undefined) return 332 if (res === undefined) return
329 333
330 return this.buildVideoPlayer(res.videoResponse, res.captionsPromise) 334 return this.buildVideoPlayer(res.videoResponse, res.captionsPromise)
@@ -386,6 +390,22 @@ export class PeerTubeEmbed {
386 390
387 this.loadParams(videoInfo) 391 this.loadParams(videoInfo)
388 392
393 const playlistPlugin = this.currentPlaylistElement
394 ? {
395 elements: this.playlistElements,
396 playlist: this.playlist,
397
398 getCurrentPosition: () => this.currentPlaylistElement.position,
399
400 onItemClicked: (videoPlaylistElement: VideoPlaylistElement) => {
401 this.currentPlaylistElement = videoPlaylistElement
402
403 this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
404 .catch(err => console.error(err))
405 }
406 }
407 : undefined
408
389 const options: PeertubePlayerManagerOptions = { 409 const options: PeertubePlayerManagerOptions = {
390 common: { 410 common: {
391 // Autoplay in playlist mode 411 // Autoplay in playlist mode
@@ -399,6 +419,7 @@ export class PeerTubeEmbed {
399 subtitle: this.subtitle, 419 subtitle: this.subtitle,
400 420
401 nextVideo: () => this.autoplayNext(), 421 nextVideo: () => this.autoplayNext(),
422 playlist: playlistPlugin,
402 423
403 videoCaptions, 424 videoCaptions,
404 inactivityTimeout: 2500, 425 inactivityTimeout: 2500,
@@ -452,6 +473,7 @@ export class PeerTubeEmbed {
452 473
453 if (this.isPlaylistEmbed()) { 474 if (this.isPlaylistEmbed()) {
454 await this.buildPlaylistManager() 475 await this.buildPlaylistManager()
476 this.player.playlist().updateSelected()
455 } 477 }
456 } 478 }
457 479
@@ -480,10 +502,7 @@ export class PeerTubeEmbed {
480 videoId = this.getResourceId() 502 videoId = this.getResourceId()
481 } 503 }
482 504
483 const res = await this.loadVideo(videoId) 505 return this.loadVideoAndBuildPlayer(videoId)
484 if (res === undefined) return
485
486 return this.buildVideoPlayer(res.videoResponse, res.captionsPromise)
487 } 506 }
488 507
489 private handleError (err: Error, translations?: { [ id: string ]: string }) { 508 private handleError (err: Error, translations?: { [ id: string ]: string }) {