]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Handle basic playlist in embed
authorChocobozzz <me@florianbigard.com>
Wed, 5 Aug 2020 07:44:58 +0000 (09:44 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Fri, 7 Aug 2020 06:58:29 +0000 (08:58 +0200)
13 files changed:
client/src/assets/player/images/tick-white.svg
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/peertube-videojs-typings.ts
client/src/assets/player/playlist/playlist-button.ts [new file with mode: 0644]
client/src/assets/player/playlist/playlist-menu-item.ts [new file with mode: 0644]
client/src/assets/player/playlist/playlist-menu.ts [new file with mode: 0644]
client/src/assets/player/playlist/playlist-plugin.ts [new file with mode: 0644]
client/src/sass/include/_miniature.scss
client/src/sass/include/_mixins.scss
client/src/sass/player/index.scss
client/src/sass/player/playlist.scss [new file with mode: 0644]
client/src/standalone/videos/embed.ts
scripts/i18n/create-custom-files.ts

index d329e6bfb3fa60fbc77bfce2de90af979f0abfb7..8868a2481f0362fc5b97c58241c632aa7df35327 100644 (file)
@@ -1,8 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <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">
 <?xml version="1.0" encoding="UTF-8"?>
 <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">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-356.000000, -115.000000)" stroke="#fff" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-356.000000, -115.000000)" stroke="#fff" stroke-width="2">
             <g id="8" transform="translate(356.000000, 115.000000)">
                 <path d="M21,6 L9,18" id="Path-14"></path>
                 <path d="M9,13 L4,18" id="Path-14" transform="translate(6.500000, 15.500000) scale(-1, 1) translate(-6.500000, -15.500000) "></path>
             <g id="8" transform="translate(356.000000, 115.000000)">
                 <path d="M21,6 L9,18" id="Path-14"></path>
                 <path d="M9,13 L4,18" id="Path-14" transform="translate(6.500000, 15.500000) scale(-1, 1) translate(-6.500000, -15.500000) "></path>
index 6a6d6346276e5ab06ba561c8992e301c694978be..dcfa3a59316f913f23f2c85b5f35ccfc7f8d9c18 100644 (file)
@@ -18,14 +18,21 @@ import './videojs-components/settings-menu-item'
 import './videojs-components/settings-panel'
 import './videojs-components/settings-panel-child'
 import './videojs-components/theater-button'
 import './videojs-components/settings-panel'
 import './videojs-components/settings-panel-child'
 import './videojs-components/theater-button'
+import './playlist/playlist-plugin'
 import videojs from 'video.js'
 import videojs from 'video.js'
-import { VideoFile } from '@shared/models'
 import { isDefaultLocale } from '@shared/core-utils/i18n'
 import { isDefaultLocale } from '@shared/core-utils/i18n'
+import { VideoFile } from '@shared/models'
 import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
 import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
 import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
 import { getStoredP2PEnabled } from './peertube-player-local-storage'
 import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
 import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
 import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
 import { getStoredP2PEnabled } from './peertube-player-local-storage'
-import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions } from './peertube-videojs-typings'
+import {
+  P2PMediaLoaderPluginOptions,
+  PlaylistPluginOptions,
+  UserWatching,
+  VideoJSCaption,
+  VideoJSPluginOptions
+} from './peertube-videojs-typings'
 import { TranslationsManager } from './translations-manager'
 import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils'
 
 import { TranslationsManager } from './translations-manager'
 import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils'
 
@@ -71,6 +78,9 @@ export interface CommonOptions extends CustomizationOptions {
 
   autoplay: boolean
   nextVideo?: Function
 
   autoplay: boolean
   nextVideo?: Function
+
+  playlist?: PlaylistPluginOptions
+
   videoDuration: number
   enableHotkeys: boolean
   inactivityTimeout: number
   videoDuration: number
   enableHotkeys: boolean
   inactivityTimeout: number
@@ -203,6 +213,10 @@ export class PeertubePlayerManager {
       }
     }
 
       }
     }
 
+    if (commonOptions.playlist) {
+      plugins.playlist = commonOptions.playlist
+    }
+
     if (commonOptions.enableHotkeys === true) {
       PeertubePlayerManager.addHotkeysOptions(plugins)
     }
     if (commonOptions.enableHotkeys === true) {
       PeertubePlayerManager.addHotkeysOptions(plugins)
     }
index 1506a04ac305cec3f620b549008ab96e058f8d84..b72c4b0f94ee4fa4a0da82a5625629e5b1dd4128 100644 (file)
@@ -1,10 +1,11 @@
 import { Config, Level } from 'hls.js'
 import videojs from 'video.js'
 import { Config, Level } from 'hls.js'
 import videojs from 'video.js'
-import { VideoFile } from '@shared/models'
+import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models'
 import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin'
 import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
 import { PlayerMode } from './peertube-player-manager'
 import { PeerTubePlugin } from './peertube-plugin'
 import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin'
 import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
 import { PlayerMode } from './peertube-player-manager'
 import { PeerTubePlugin } from './peertube-plugin'
+import { PlaylistPlugin } from './playlist/playlist-plugin'
 import { EndCardOptions } from './upnext/end-card'
 import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
 
 import { EndCardOptions } from './upnext/end-card'
 import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
 
@@ -45,6 +46,8 @@ declare module 'video.js' {
     dock (options: { title: string, description: string }): void
 
     upnext (options: Partial<EndCardOptions>): void
     dock (options: { title: string, description: string }): void
 
     upnext (options: Partial<EndCardOptions>): void
+
+    playlist (): PlaylistPlugin
   }
 }
 
   }
 }
 
@@ -105,6 +108,16 @@ type PeerTubePluginOptions = {
   stopTime: number | string
 }
 
   stopTime: number | string
 }
 
+type PlaylistPluginOptions = {
+  elements: VideoPlaylistElement[]
+
+  playlist: VideoPlaylist
+
+  getCurrentPosition: () => number
+
+  onItemClicked: (element: VideoPlaylistElement) => void
+}
+
 type WebtorrentPluginOptions = {
   playerElement: HTMLVideoElement
 
 type WebtorrentPluginOptions = {
   playerElement: HTMLVideoElement
 
@@ -125,6 +138,8 @@ type P2PMediaLoaderPluginOptions = {
 }
 
 type VideoJSPluginOptions = {
 }
 
 type VideoJSPluginOptions = {
+  playlist?: PlaylistPluginOptions
+
   peertube: PeerTubePluginOptions
 
   webtorrent?: WebtorrentPluginOptions
   peertube: PeerTubePluginOptions
 
   webtorrent?: WebtorrentPluginOptions
@@ -170,10 +185,18 @@ type PlayerNetworkInfo = {
   }
 }
 
   }
 }
 
+type PlaylistItemOptions = {
+  element: VideoPlaylistElement
+
+  onClicked: Function
+}
+
 export {
   PlayerNetworkInfo,
 export {
   PlayerNetworkInfo,
+  PlaylistItemOptions,
   ResolutionUpdateData,
   AutoResolutionUpdateData,
   ResolutionUpdateData,
   AutoResolutionUpdateData,
+  PlaylistPluginOptions,
   VideoJSCaption,
   UserWatching,
   PeerTubePluginOptions,
   VideoJSCaption,
   UserWatching,
   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 (file)
index 0000000..a7996ec
--- /dev/null
@@ -0,0 +1,61 @@
+import videojs from 'video.js'
+import { PlaylistPluginOptions } from '../peertube-videojs-typings'
+import { PlaylistMenu } from './playlist-menu'
+
+const ClickableComponent = videojs.getComponent('ClickableComponent')
+
+class PlaylistButton extends ClickableComponent {
+  private playlistInfoElement: HTMLElement
+  private wrapper: HTMLElement
+
+  constructor (player: videojs.Player, options?: PlaylistPluginOptions & { playlistMenu: PlaylistMenu }) {
+    super(player, options as any)
+  }
+
+  createEl () {
+    this.wrapper = super.createEl('div', {
+      className: 'vjs-playlist-button',
+      innerHTML: '',
+      tabIndex: -1
+    }) as HTMLElement
+
+    const icon = super.createEl('div', {
+      className: 'vjs-playlist-icon',
+      innerHTML: '',
+      tabIndex: -1
+    })
+
+    this.playlistInfoElement = super.createEl('div', {
+      className: 'vjs-playlist-info',
+      innerHTML: '',
+      tabIndex: -1
+    }) as HTMLElement
+
+    this.wrapper.appendChild(icon)
+    this.wrapper.appendChild(this.playlistInfoElement)
+
+    this.update()
+
+    return this.wrapper
+  }
+
+  update () {
+    const options = this.options_ as PlaylistPluginOptions
+
+    this.playlistInfoElement.innerHTML = options.getCurrentPosition() + '/' + options.playlist.videosLength
+    this.wrapper.title = this.player().localize('Playlist: {1}', [ options.playlist.displayName ])
+  }
+
+  handleClick () {
+    const playlistMenu = this.getPlaylistMenu()
+    playlistMenu.open()
+  }
+
+  private getPlaylistMenu () {
+    return (this.options_ as any).playlistMenu as PlaylistMenu
+  }
+}
+
+videojs.registerComponent('PlaylistButton', PlaylistButton)
+
+export { 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 (file)
index 0000000..916c633
--- /dev/null
@@ -0,0 +1,98 @@
+import videojs from 'video.js'
+import { VideoPlaylistElement } from '@shared/models'
+import { PlaylistItemOptions } from '../peertube-videojs-typings'
+
+const Component = videojs.getComponent('Component')
+
+class PlaylistMenuItem extends Component {
+  private element: VideoPlaylistElement
+
+  constructor (player: videojs.Player, options?: PlaylistItemOptions) {
+    super(player, options as any)
+
+    this.emitTapEvents()
+
+    this.element = options.element
+
+    this.on([ 'click', 'tap' ], () => this.switchPlaylistItem())
+    this.on('keydown', event => this.handleKeyDown(event))
+  }
+
+  createEl () {
+    const options = this.options_ as PlaylistItemOptions
+
+    const li = super.createEl('li', {
+      className: 'vjs-playlist-menu-item',
+      innerHTML: ''
+    }) as HTMLElement
+
+    const positionBlock = super.createEl('div', {
+      className: 'item-position-block'
+    })
+
+    const position = super.createEl('div', {
+      className: 'item-position',
+      innerHTML: options.element.position
+    })
+
+    const player = super.createEl('div', {
+      className: 'item-player'
+    })
+
+    positionBlock.appendChild(position)
+    positionBlock.appendChild(player)
+
+    li.appendChild(positionBlock)
+
+    const thumbnail = super.createEl('img', {
+      src: window.location.origin + options.element.video.thumbnailPath
+    })
+
+    const infoBlock = super.createEl('div', {
+      className: 'info-block'
+    })
+
+    const title = super.createEl('div', {
+      innerHTML: options.element.video.name,
+      className: 'title'
+    })
+
+    const channel = super.createEl('div', {
+      innerHTML: options.element.video.channel.displayName,
+      className: 'channel'
+    })
+
+    infoBlock.appendChild(title)
+    infoBlock.appendChild(channel)
+
+    li.append(thumbnail)
+    li.append(infoBlock)
+
+    return li
+  }
+
+  setSelected (selected: boolean) {
+    if (selected) this.addClass('vjs-selected')
+    else this.removeClass('vjs-selected')
+  }
+
+  getElement () {
+    return this.element
+  }
+
+  private handleKeyDown (event: KeyboardEvent) {
+    if (event.code === 'Space' || event.code === 'Enter') {
+      this.switchPlaylistItem()
+    }
+  }
+
+  private switchPlaylistItem () {
+    const options = this.options_ as PlaylistItemOptions
+
+    options.onClicked()
+  }
+}
+
+Component.registerComponent('PlaylistMenuItem', PlaylistMenuItem)
+
+export { 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 (file)
index 0000000..7d7d9e1
--- /dev/null
@@ -0,0 +1,124 @@
+import videojs from 'video.js'
+import { VideoPlaylistElement } from '@shared/models'
+import { PlaylistPluginOptions } from '../peertube-videojs-typings'
+import { PlaylistMenuItem } from './playlist-menu-item'
+
+const Component = videojs.getComponent('Component')
+
+class PlaylistMenu extends Component {
+  private menuItems: PlaylistMenuItem[]
+
+  constructor (player: videojs.Player, options?: PlaylistPluginOptions) {
+    super(player, options as any)
+
+    this.player().on('userinactive', () => {
+      this.close()
+    })
+
+    this.player().on('click', event => {
+      let current = event.target as HTMLElement
+
+      do {
+        if (
+          current.classList.contains('vjs-playlist-menu') ||
+          current.classList.contains('vjs-playlist-button')
+        ) {
+          return
+        }
+
+        current = current.parentElement
+      } while (current)
+
+      this.close()
+    })
+  }
+
+  createEl () {
+    this.menuItems = []
+
+    const options = this.getOptions()
+
+    const menu = super.createEl('div', {
+      className: 'vjs-playlist-menu',
+      innerHTML: '',
+      tabIndex: -1
+    })
+
+    const header = super.createEl('div', {
+      className: 'header'
+    })
+
+    const headerLeft = super.createEl('div')
+
+    const leftTitle = super.createEl('div', {
+      innerHTML: options.playlist.displayName,
+      className: 'title'
+    })
+
+    const leftSubtitle = super.createEl('div', {
+      innerHTML: this.player().localize('By {1}', [ options.playlist.videoChannel.displayName ]),
+      className: 'channel'
+    })
+
+    headerLeft.appendChild(leftTitle)
+    headerLeft.appendChild(leftSubtitle)
+
+    const tick = super.createEl('div', {
+      className: 'cross'
+    })
+    tick.addEventListener('click', () => this.close())
+
+    header.appendChild(headerLeft)
+    header.appendChild(tick)
+
+    const list = super.createEl('ol')
+
+    for (const playlistElement of options.elements) {
+      const item = new PlaylistMenuItem(this.player(), {
+        element: playlistElement,
+        onClicked: () => this.onItemClicked(playlistElement)
+      })
+
+      list.appendChild(item.el())
+
+      this.menuItems.push(item)
+    }
+
+    menu.appendChild(header)
+    menu.appendChild(list)
+
+    return menu
+  }
+
+  update () {
+    const options = this.getOptions()
+
+    this.updateSelected(options.getCurrentPosition())
+  }
+
+  open () {
+    this.player().addClass('playlist-menu-displayed')
+  }
+
+  close () {
+    this.player().removeClass('playlist-menu-displayed')
+  }
+
+  updateSelected (newPosition: number) {
+    for (const item of this.menuItems) {
+      item.setSelected(item.getElement().position === newPosition)
+    }
+  }
+
+  private getOptions () {
+    return this.options_ as PlaylistPluginOptions
+  }
+
+  private onItemClicked (element: VideoPlaylistElement) {
+    this.getOptions().onItemClicked(element)
+  }
+}
+
+Component.registerComponent('PlaylistMenu', PlaylistMenu)
+
+export { 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 (file)
index 0000000..b69d82e
--- /dev/null
@@ -0,0 +1,35 @@
+import videojs from 'video.js'
+import { PlaylistPluginOptions } from '../peertube-videojs-typings'
+import { PlaylistButton } from './playlist-button'
+import { PlaylistMenu } from './playlist-menu'
+
+const Plugin = videojs.getPlugin('plugin')
+
+class PlaylistPlugin extends Plugin {
+  private playlistMenu: PlaylistMenu
+  private playlistButton: PlaylistButton
+  private options: PlaylistPluginOptions
+
+  constructor (player: videojs.Player, options?: PlaylistPluginOptions) {
+    super(player, options)
+
+    this.options = options
+
+    this.player.ready(() => {
+      player.addClass('vjs-playlist')
+    })
+
+    this.playlistMenu = new PlaylistMenu(player, options)
+    this.playlistButton = new PlaylistButton(player, Object.assign({}, options, { playlistMenu: this.playlistMenu }))
+
+    player.addChild(this.playlistMenu, options)
+    player.addChild(this.playlistButton, options)
+  }
+
+  updateSelected () {
+    this.playlistMenu.updateSelected(this.options.getCurrentPosition())
+  }
+}
+
+videojs.registerPlugin('playlist', PlaylistPlugin)
+export { PlaylistPlugin }
index 976bbf4d68b1b47e5334e5517610a9fe43e72683..97b4c690b1dd96d2dad891661f5352c3273af04e 100644 (file)
@@ -52,18 +52,7 @@ $play-overlay-width: 18px;
     }
 
     .icon {
     }
 
     .icon {
-      width: 0;
-      height: 0;
-
-      position: absolute;
-      left: 50%;
-      top: 50%;
-      transform: translate(-50%, -50%) scale(0.5);
-
-      border-top: ($play-overlay-height / 2) solid transparent;
-      border-bottom: ($play-overlay-height / 2) solid transparent;
-
-      border-left: $play-overlay-width solid rgba(255, 255, 255, 0.95);
+      @include play-icon($play-overlay-height, $play-overlay-width);
     }
   }
 
     }
   }
 
index ee2fe04978ace5eeccbc32f4cd252a89df0c928a..e4c2dffa0f0e83794621bb9055ce9a5772b4967f 100644 (file)
     }
   }
 }
     }
   }
 }
+
+@mixin play-icon ($width, $height) {
+  width: 0;
+  height: 0;
+
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%) scale(0.5);
+
+  border-top: ($height / 2) solid transparent;
+  border-bottom: ($height / 2) solid transparent;
+
+  border-left: $width solid rgba(255, 255, 255, 0.95);
+}
index 58ce3ac96874e0d6816a1319dfe4e63c54af7e1a..fe92ce5e0637c7bf9c4c1434fa8da49431d7a515 100644 (file)
@@ -4,4 +4,5 @@
 @import './settings-menu';
 @import './spinner';
 @import './upnext';
 @import './settings-menu';
 @import './spinner';
 @import './upnext';
-@import './bezels.scss';
\ No newline at end of file
+@import './bezels.scss';
+@import './playlist.scss';
diff --git a/client/src/sass/player/playlist.scss b/client/src/sass/player/playlist.scss
new file mode 100644 (file)
index 0000000..c242acb
--- /dev/null
@@ -0,0 +1,165 @@
+$playlist-menu-width: 350px;
+
+.vjs-playlist-menu {
+  position: absolute;
+  right: 0;
+  height: 100%;
+  width: $playlist-menu-width;
+  background: rgba(0, 0, 0, 0.8);
+  z-index: 101;
+  transition: right 0.2s;
+
+  // Hidden
+  right: -$playlist-menu-width;
+
+  ol {
+    padding: 0;
+    margin: 0;
+  }
+
+  .header {
+    border-bottom: 1px solid $header-border-color;
+    padding: 20px 10px;
+    display: flex;
+    justify-content: space-between;
+
+    .title {
+      font-size: 14px;
+      margin-bottom: 5px;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+
+    .channel {
+      font-size: 11px;
+      color: #bfbfbf;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+
+    .cross {
+      cursor: pointer;
+      width: 20px;
+      height: 20px;
+      mask-image: url('#{$assets-path}/images/feather/x.svg');
+      -webkit-mask-image: url('#{$assets-path}/images/feather/x.svg');
+      background-color: white;
+      mask-size: cover;
+      -webkit-mask-size: cover;
+    }
+  }
+}
+
+.playlist-menu-displayed {
+
+  .vjs-playlist-menu {
+    right: 0;
+    display: block;
+  }
+
+  .vjs-playlist-button {
+    display: none;
+  }
+}
+
+@media screen and (max-width: $playlist-menu-width) {
+  .vjs-playlist-menu {
+    width: 100%;
+    min-width: unset;
+    display: none;
+  }
+
+  .playlist-menu-displayed .vjs-playlist-menu {
+    display: block;
+  }
+}
+
+.vjs-playlist-button {
+  font-size: 15px;
+  position: absolute;
+  right: 0;
+  top: 0;
+  z-index: 100;
+  padding: 1em;
+  cursor: pointer;
+}
+
+.vjs-playlist-icon {
+  width: 22px;
+  height: 22px;
+  mask-image: url('#{$assets-path}/images/feather/list.svg');
+  -webkit-mask-image: url('#{$assets-path}/images/feather/list.svg');
+  background-color: white;
+  mask-size: cover;
+  -webkit-mask-size: cover;
+  margin-bottom: 3px;
+}
+
+.vjs-playing.vjs-user-inactive .vjs-playlist-button {
+  opacity: 0;
+
+  transition: opacity 1s;
+}
+
+.vjs-playing.vjs-no-flex.vjs-user-inactive .vjs-playlist-button {
+  display: none;
+}
+
+.vjs-playlist-menu-item {
+  cursor: pointer;
+  display: flex;
+  padding: 10px 0;
+
+  .item-position-block {
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 30px;
+  }
+
+  .item-player {
+    display: none;
+
+    @include play-icon(20px, 16px);
+  }
+
+  &.vjs-selected {
+    background-color: rgba(150, 150, 150, 0.3);
+
+    .item-position {
+      display: none;
+    }
+
+    .item-player {
+      display: block;
+    }
+  }
+
+  &:hover {
+    background-color: rgba(150, 150, 150, 0.2);
+  }
+
+  img {
+    width: 80px;
+    height: 40px;
+  }
+
+  .info-block {
+    margin-left: 10px;
+
+    .title {
+      font-size: 13px;
+      margin-bottom: 5px;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+
+    .channel {
+      font-size: 11px;
+      color: #bfbfbf;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+  }
+}
index 71bd04e764378dca8cc525deadf1fb2f14598562..17b0ee9ef03053c2da4bd6cd839a519745da097f 100644 (file)
@@ -324,7 +324,11 @@ export class PeerTubeEmbed {
 
     this.currentPlaylistElement = next
 
 
     this.currentPlaylistElement = next
 
-    const res = await this.loadVideo(this.currentPlaylistElement.video.uuid)
+    return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
+  }
+
+  private async loadVideoAndBuildPlayer (uuid: string) {
+    const res = await this.loadVideo(uuid)
     if (res === undefined) return
 
     return this.buildVideoPlayer(res.videoResponse, res.captionsPromise)
     if (res === undefined) return
 
     return this.buildVideoPlayer(res.videoResponse, res.captionsPromise)
@@ -386,6 +390,22 @@ export class PeerTubeEmbed {
 
     this.loadParams(videoInfo)
 
 
     this.loadParams(videoInfo)
 
+    const playlistPlugin = this.currentPlaylistElement
+      ? {
+        elements: this.playlistElements,
+        playlist: this.playlist,
+
+        getCurrentPosition: () => this.currentPlaylistElement.position,
+
+        onItemClicked: (videoPlaylistElement: VideoPlaylistElement) => {
+          this.currentPlaylistElement = videoPlaylistElement
+
+          this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
+            .catch(err => console.error(err))
+        }
+      }
+      : undefined
+
     const options: PeertubePlayerManagerOptions = {
       common: {
         // Autoplay in playlist mode
     const options: PeertubePlayerManagerOptions = {
       common: {
         // Autoplay in playlist mode
@@ -399,6 +419,7 @@ export class PeerTubeEmbed {
         subtitle: this.subtitle,
 
         nextVideo: () => this.autoplayNext(),
         subtitle: this.subtitle,
 
         nextVideo: () => this.autoplayNext(),
+        playlist: playlistPlugin,
 
         videoCaptions,
         inactivityTimeout: 2500,
 
         videoCaptions,
         inactivityTimeout: 2500,
@@ -452,6 +473,7 @@ export class PeerTubeEmbed {
 
     if (this.isPlaylistEmbed()) {
       await this.buildPlaylistManager()
 
     if (this.isPlaylistEmbed()) {
       await this.buildPlaylistManager()
+      this.player.playlist().updateSelected()
     }
   }
 
     }
   }
 
@@ -480,10 +502,7 @@ export class PeerTubeEmbed {
       videoId = this.getResourceId()
     }
 
       videoId = this.getResourceId()
     }
 
-    const res = await this.loadVideo(videoId)
-    if (res === undefined) return
-
-    return this.buildVideoPlayer(res.videoResponse, res.captionsPromise)
+    return this.loadVideoAndBuildPlayer(videoId)
   }
 
   private handleError (err: Error, translations?: { [ id: string ]: string }) {
   }
 
   private handleError (err: Error, translations?: { [ id: string ]: string }) {
index 298eda71b4fd8c53f6196530ca0d072d296a3e3d..89a967b148b194f96037af449f45b91c7aaceffb 100755 (executable)
@@ -50,7 +50,9 @@ values(VIDEO_CATEGORIES)
     'Sorry',
     'This video is not available because the remote instance is not responding.',
     'This playlist does not exist',
     'Sorry',
     'This video is not available because the remote instance is not responding.',
     'This playlist does not exist',
-    'We cannot fetch the playlist. Please try again later.'
+    'We cannot fetch the playlist. Please try again later.',
+    'Playlist: {1}',
+    'By {1}'
   ])
   .forEach(v => { serverKeys[v] = v })
 
   ])
   .forEach(v => { serverKeys[v] = v })