]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add previous button
authorChocobozzz <me@florianbigard.com>
Wed, 5 Aug 2020 09:02:14 +0000 (11:02 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Fri, 7 Aug 2020 06:58:29 +0000 (08:58 +0200)
client/src/assets/player/images/next.svg
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/peertube-videojs-typings.ts
client/src/assets/player/videojs-components/next-previous-video-button.ts [new file with mode: 0644]
client/src/assets/player/videojs-components/next-video-button.ts [deleted file]
client/src/sass/player/peertube-skin.scss
client/src/standalone/videos/embed.ts

index af42dd27073b5fcf34fbb701d9c5852adda0ad7c..0441f93c8d12ff16e07e7878a60ffae177060c11 100644 (file)
@@ -1,4 +1,59 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <path fill="white" d="M 12,24 20.5,18 12,12 V 24 z M 22,12 v 12 h 2 V 12 h -2 z"></path>
-</svg>
\ No newline at end of file
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
+   sodipodi:docname="next.svg"
+   id="svg4"
+   version="1.1"
+   viewBox="0 0 12 12"
+   height="8"
+   width="8">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     inkscape:current-layer="svg4"
+     inkscape:window-maximized="1"
+     inkscape:window-y="0"
+     inkscape:window-x="0"
+     inkscape:cy="-2.5620165"
+     inkscape:cx="-7.4038126"
+     inkscape:zoom="29.791667"
+     fit-margin-bottom="0"
+     fit-margin-right="0"
+     fit-margin-left="0"
+     fit-margin-top="0"
+     showgrid="false"
+     id="namedview6"
+     inkscape:window-height="1037"
+     inkscape:window-width="1916"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0"
+     guidetolerance="10"
+     gridtolerance="10"
+     objecttolerance="10"
+     borderopacity="1"
+     bordercolor="#666666"
+     pagecolor="#ffffff" />
+  <path
+     id="path2"
+     d="M 0,12 8.5,6 0,0 Z M 10,0 v 12 h 2 V 0 Z"
+     fill="#ffffff" />
+</svg>
index dcfa3a59316f913f23f2c85b5f35ccfc7f8d9c18..c71b4341554307e793e0410e4b23bf4e5efd4dd5 100644 (file)
@@ -6,7 +6,7 @@ import './upnext/end-card'
 import './upnext/upnext-plugin'
 import './bezels/bezels-plugin'
 import './peertube-plugin'
-import './videojs-components/next-video-button'
+import './videojs-components/next-previous-video-button'
 import './videojs-components/p2p-info-button'
 import './videojs-components/peertube-link-button'
 import './videojs-components/peertube-load-progress-bar'
@@ -27,6 +27,7 @@ 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 {
+  NextPreviousVideoButtonOptions,
   P2PMediaLoaderPluginOptions,
   PlaylistPluginOptions,
   UserWatching,
@@ -77,7 +78,12 @@ export interface CommonOptions extends CustomizationOptions {
   onPlayerElementChange: (element: HTMLVideoElement) => void
 
   autoplay: boolean
-  nextVideo?: Function
+
+  nextVideo?: () => void
+  hasNextVideo?: () => boolean
+
+  previousVideo?: () => void
+  hasPreviousVideo?: () => boolean
 
   playlist?: PlaylistPluginOptions
 
@@ -259,7 +265,12 @@ export class PeertubePlayerManager {
           captions: commonOptions.captions,
           peertubeLink: commonOptions.peertubeLink,
           theaterButton: commonOptions.theaterButton,
-          nextVideo: commonOptions.nextVideo
+
+          nextVideo: commonOptions.nextVideo,
+          hasNextVideo: commonOptions.hasNextVideo,
+
+          previousVideo: commonOptions.previousVideo,
+          hasPreviousVideo: commonOptions.hasPreviousVideo
         }) as any // FIXME: typings
       }
     }
@@ -360,9 +371,14 @@ export class PeertubePlayerManager {
 
   private static getControlBarChildren (mode: PlayerMode, options: {
     peertubeLink: boolean
-    theaterButton: boolean,
-    captions: boolean,
+    theaterButton: boolean
+    captions: boolean
+
     nextVideo?: Function
+    hasNextVideo?: () => boolean
+
+    previousVideo?: Function
+    hasPreviousVideo?: () => boolean
   }) {
     const settingEntries = []
     const loadProgressBar = mode === 'webtorrent' ? 'peerTubeLoadProgressBar' : 'loadProgressBar'
@@ -372,15 +388,39 @@ export class PeertubePlayerManager {
     if (options.captions === true) settingEntries.push('captionsButton')
     settingEntries.push('resolutionMenuButton')
 
-    const children = {
-      'playToggle': {}
+    const children = {}
+
+    if (options.previousVideo) {
+      const buttonOptions: NextPreviousVideoButtonOptions = {
+        type: 'previous',
+        handler: options.previousVideo,
+        isDisabled: () => {
+          if (!options.hasPreviousVideo) return false
+
+          return !options.hasPreviousVideo()
+        }
+      }
+
+      Object.assign(children, {
+        'previousVideoButton': buttonOptions
+      })
     }
 
+    Object.assign(children, { playToggle: {} })
+
     if (options.nextVideo) {
-      Object.assign(children, {
-        'nextVideoButton': {
-          handler: options.nextVideo
+      const buttonOptions: NextPreviousVideoButtonOptions = {
+        type: 'next',
+        handler: options.nextVideo,
+        isDisabled: () => {
+          if (!options.hasNextVideo) return false
+
+          return !options.hasNextVideo()
         }
+      }
+
+      Object.assign(children, {
+        'nextVideoButton': buttonOptions
       })
     }
 
index b72c4b0f94ee4fa4a0da82a5625629e5b1dd4128..a359b8595f4c553f351fbec7be9f5cce5e9777b3 100644 (file)
@@ -118,6 +118,12 @@ type PlaylistPluginOptions = {
   onItemClicked: (element: VideoPlaylistElement) => void
 }
 
+type NextPreviousVideoButtonOptions = {
+  type: 'next' | 'previous'
+  handler: Function
+  isDisabled: () => boolean
+}
+
 type WebtorrentPluginOptions = {
   playerElement: HTMLVideoElement
 
@@ -194,6 +200,7 @@ type PlaylistItemOptions = {
 export {
   PlayerNetworkInfo,
   PlaylistItemOptions,
+  NextPreviousVideoButtonOptions,
   ResolutionUpdateData,
   AutoResolutionUpdateData,
   PlaylistPluginOptions,
diff --git a/client/src/assets/player/videojs-components/next-previous-video-button.ts b/client/src/assets/player/videojs-components/next-previous-video-button.ts
new file mode 100644 (file)
index 0000000..fe17ce2
--- /dev/null
@@ -0,0 +1,50 @@
+import videojs from 'video.js'
+import { NextPreviousVideoButtonOptions } from '../peertube-videojs-typings'
+
+const Button = videojs.getComponent('Button')
+
+class NextPreviousVideoButton extends Button {
+  private readonly nextPreviousVideoButtonOptions: NextPreviousVideoButtonOptions
+
+  constructor (player: videojs.Player, options?: NextPreviousVideoButtonOptions) {
+    super(player, options as any)
+
+    this.nextPreviousVideoButtonOptions = options
+
+    this.update()
+  }
+
+  createEl () {
+    const type = (this.options_ as NextPreviousVideoButtonOptions).type
+
+    const button = videojs.dom.createEl('button', {
+      className: 'vjs-' + type + '-video'
+    }) as HTMLButtonElement
+    const nextIcon = videojs.dom.createEl('span', {
+      className: 'icon icon-' + type
+    })
+    button.appendChild(nextIcon)
+
+    if (type === 'next') {
+      button.title = this.player_.localize('Next video')
+    } else {
+      button.title = this.player_.localize('Previous video')
+    }
+
+    return button
+  }
+
+  handleClick () {
+    this.nextPreviousVideoButtonOptions.handler()
+  }
+
+  update () {
+    const disabled = this.nextPreviousVideoButtonOptions.isDisabled()
+
+    if (disabled) this.addClass('vjs-disabled')
+    else this.removeClass('vjs-disabled')
+  }
+}
+
+videojs.registerComponent('NextVideoButton', NextPreviousVideoButton)
+videojs.registerComponent('PreviousVideoButton', NextPreviousVideoButton)
diff --git a/client/src/assets/player/videojs-components/next-video-button.ts b/client/src/assets/player/videojs-components/next-video-button.ts
deleted file mode 100644 (file)
index 22b32f0..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import videojs from 'video.js'
-
-const Button = videojs.getComponent('Button')
-
-export interface NextVideoButtonOptions extends videojs.ComponentOptions {
-  handler: Function
-}
-
-class NextVideoButton extends Button {
-  private readonly nextVideoButtonOptions: NextVideoButtonOptions
-
-  constructor (player: videojs.Player, options?: NextVideoButtonOptions) {
-    super(player, options)
-
-    this.nextVideoButtonOptions = options
-  }
-
-  createEl () {
-    const button = videojs.dom.createEl('button', {
-      className: 'vjs-next-video'
-    }) as HTMLButtonElement
-    const nextIcon = videojs.dom.createEl('span', {
-      className: 'icon icon-next'
-    })
-    button.appendChild(nextIcon)
-
-    button.title = this.player_.localize('Next video')
-
-    return button
-  }
-
-  handleClick () {
-    this.nextVideoButtonOptions.handler()
-  }
-}
-
-videojs.registerComponent('NextVideoButton', NextVideoButton)
index 2c22239a092a2c8917ed9b95530a3429c095ee1a..994936f81482f2286d1be48dd54c223bbb27d1f5 100644 (file)
@@ -147,6 +147,10 @@ body {
     box-shadow: 0 -15px 40px 10px rgba(0, 0, 0, 0.2);
     text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
 
+    > button:first-child {
+      margin-left: 1em;
+    }
+
     .vjs-progress-control,
     .vjs-play-control,
     .vjs-playback-rate,
@@ -230,7 +234,6 @@ body {
 
       cursor: pointer;
       font-size: $font-size;
-      margin-left: 1em;
       width: 3em;
     }
 
@@ -301,24 +304,32 @@ body {
       }
     }
 
-    .vjs-next-video {
+    .vjs-next-video,
+    .vjs-previous-video {
       line-height: $control-bar-height;
       text-align: right;
 
       .icon {
-        &.icon-next {
+        &.icon-next,
+        &.icon-previous {
           mask-image: url('#{$assets-path}/player/images/next.svg');
           -webkit-mask-image: url('#{$assets-path}/player/images/next.svg');
           background-color: white;
           mask-size: cover;
           -webkit-mask-size: cover;
-          transform: scale(2.2);
+          width: 11px;
+          height: 11px;
+          margin-top: -2px;
+          display: inline-block;
+        }
+
+        &.icon-previous {
+          transform: rotate(180deg);
         }
       }
     }
 
-    .vjs-peertube,
-    .vjs-next-video {
+    .vjs-peertube {
       .icon {
         display: inline-block;
         width: 15px;
@@ -650,3 +661,13 @@ body {
     display: block;
   }
 }
+
+.vjs-no-next-in-playlist {
+  .vjs-next-video {
+    cursor: default;
+
+    .icon {
+      background-color: rgba(255, 255, 255, 0.5);
+    }
+  }
+}
index 17b0ee9ef03053c2da4bd6cd839a519745da097f..786d749a498903ca9181029fdb03dde864b1e111 100644 (file)
@@ -309,13 +309,13 @@ export class PeerTubeEmbed {
       cancelText: peertubeTranslate('Cancel', translations),
       suspendedText: peertubeTranslate('Autoplay is suspended', translations),
       getTitle: () => this.nextVideoTitle(),
-      next: () => this.autoplayNext(),
+      next: () => this.playNextVideo(),
       condition: () => !!this.getNextPlaylistElement(),
       suspended: () => false
     })
   }
 
-  private async autoplayNext () {
+  private async playNextVideo () {
     const next = this.getNextPlaylistElement()
     if (!next) {
       console.log('Next element not found in playlist.')
@@ -327,6 +327,18 @@ export class PeerTubeEmbed {
     return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
   }
 
+  private async playPreviousVideo () {
+    const previous = this.getPreviousPlaylistElement()
+    if (!previous) {
+      console.log('Previous element not found in playlist.')
+      return
+    }
+
+    this.currentPlaylistElement = previous
+
+    return this.loadVideoAndBuildPlayer(this.currentPlaylistElement.video.uuid)
+  }
+
   private async loadVideoAndBuildPlayer (uuid: string) {
     const res = await this.loadVideo(uuid)
     if (res === undefined) return
@@ -357,6 +369,22 @@ export class PeerTubeEmbed {
     return next
   }
 
+  private getPreviousPlaylistElement (position?: number): VideoPlaylistElement {
+    if (!position) position = this.currentPlaylistElement.position -1
+
+    if (position < 1) {
+      return undefined
+    }
+
+    const prev = this.playlistElements.find(e => e.position === position)
+
+    if (!prev || !prev.video) {
+      return this.getNextPlaylistElement(position - 1)
+    }
+
+    return prev
+  }
+
   private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) {
     let alreadyHadPlayer = false
 
@@ -418,7 +446,12 @@ export class PeerTubeEmbed {
         stopTime: this.stopTime,
         subtitle: this.subtitle,
 
-        nextVideo: () => this.autoplayNext(),
+        nextVideo: this.playlist ? () => this.playNextVideo() : undefined,
+        hasNextVideo: this.playlist ? () => !!this.getNextPlaylistElement() : undefined,
+
+        previousVideo: this.playlist ? () => this.playPreviousVideo() : undefined,
+        hasPreviousVideo: this.playlist ? () => !!this.getPreviousPlaylistElement() : undefined,
+
         playlist: playlistPlugin,
 
         videoCaptions,