Forráskód Böngészése

Add more embed parameters

Chocobozzz 5 éve
szülő
commit
5efab5467c

+ 17 - 7
client/src/app/videos/+video-watch/video-watch.component.ts

@@ -20,6 +20,7 @@ import { environment } from '../../../environments/environment'
 import { VideoCaptionService } from '@app/shared/video-caption'
 import { MarkdownService } from '@app/shared/renderer'
 import {
+  CustomizationOptions,
   P2PMediaLoaderOptions,
   PeertubePlayerManager,
   PeertubePlayerManagerOptions,
@@ -28,7 +29,7 @@ import {
 import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
 import { Video } from '@app/shared/video/video.model'
-import { isWebRTCDisabled } from '../../../assets/player/utils'
+import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils'
 import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
 
 @Component({
@@ -249,8 +250,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         const urlOptions = {
           startTime: queryParams.start,
           stopTime: queryParams.stop,
+
+          muted: queryParams.muted,
+          loop: queryParams.loop,
           subtitle: queryParams.subtitle,
-          playerMode: queryParams.mode
+
+          playerMode: queryParams.mode,
+          peertubeLink: false
         }
 
         this.onVideoFetched(video, captionsResult.data, urlOptions)
@@ -327,7 +333,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   private async onVideoFetched (
     video: VideoDetails,
     videoCaptions: VideoCaption[],
-    urlOptions: { startTime?: number, stopTime?: number, subtitle?: string, playerMode?: string }
+    urlOptions: CustomizationOptions & { playerMode: PlayerMode }
   ) {
     this.video = video
 
@@ -339,7 +345,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
     this.videoWatchPlaylist.updatePlaylistIndex(video)
 
-    let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
+    let startTime = timeToInt(urlOptions.startTime) || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
     // If we are at the end of the video, reset the timer
     if (this.video.duration - startTime <= 1) startTime = 0
 
@@ -378,12 +384,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         enableHotkeys: true,
         inactivityTimeout: 2500,
         poster: this.video.previewUrl,
+
         startTime,
         stopTime: urlOptions.stopTime,
+        controls: urlOptions.controls,
+        muted: urlOptions.muted,
+        loop: urlOptions.loop,
+        subtitle: urlOptions.subtitle,
+
+        peertubeLink: urlOptions.peertubeLink,
 
         theaterMode: true,
         captions: videoCaptions.length !== 0,
-        peertubeLink: false,
 
         videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE
           ? this.videoService.getVideoViewUrl(this.video.uuid)
@@ -392,8 +404,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
         language: this.localeId,
 
-        subtitle: urlOptions.subtitle,
-
         userWatching: this.user && this.user.videosHistoryEnabled === true ? {
           url: this.videoService.getUserWatchingVideoUrl(this.video.uuid),
           authorizationHeader: this.authService.getRequestHeaderValue()

+ 13 - 8
client/src/assets/player/peertube-player-manager.ts

@@ -39,7 +39,19 @@ export type P2PMediaLoaderOptions = {
   videoFiles: VideoFile[]
 }
 
-export type CommonOptions = {
+export interface CustomizationOptions {
+  startTime: number | string
+  stopTime: number | string
+
+  controls?: boolean
+  muted?: boolean
+  loop?: boolean
+  subtitle?: string
+
+  peertubeLink: boolean
+}
+
+export interface CommonOptions extends CustomizationOptions {
   playerElement: HTMLVideoElement
   onPlayerElementChange: (element: HTMLVideoElement) => void
 
@@ -48,21 +60,14 @@ export type CommonOptions = {
   enableHotkeys: boolean
   inactivityTimeout: number
   poster: string
-  startTime: number | string
-  stopTime: number | string
 
   theaterMode: boolean
   captions: boolean
-  peertubeLink: boolean
 
   videoViewUrl: string
   embedUrl: string
 
   language?: string
-  controls?: boolean
-  muted?: boolean
-  loop?: boolean
-  subtitle?: string
 
   videoCaptions: VideoJSCaption[]
 

+ 3 - 1
client/src/sass/include/_variables.scss

@@ -71,7 +71,9 @@ $variables: (
   --menuForegroundColor: var(--menuForegroundColor),
   --submenuColor: var(--submenuColor),
   --inputColor: var(--inputColor),
-  --inputPlaceholderColor: var(--inputPlaceholderColor)
+  --inputPlaceholderColor: var(--inputPlaceholderColor),
+  --embedForegroundColor: var(--embedForegroundColor),
+  --embedBigPlayBackgroundColor: var(--embedBigPlayBackgroundColor)
 );
 
 /*** theme helper ***/

+ 7 - 1
client/src/sass/player/_player-variables.scss

@@ -10,4 +10,10 @@ $slider-bg-color: lighten($primary-background-color, 33%);
 
 $progress-margin: 10px;
 
-$assets-path: '../../assets/' !default;
+$assets-path: '../../assets/' !default;
+
+body {
+  --embedForegroundColor: #{$primary-foreground-color};
+
+  --embedBigPlayBackgroundColor: #{$primary-background-color};
+}

+ 2 - 2
client/src/sass/player/context-menu.scss

@@ -14,7 +14,7 @@ $context-menu-width: 350px;
 
   .vjs-menu-content {
     opacity: $primary-foreground-opacity;
-    color: $primary-foreground-color;
+    color: var(--embedForegroundCsolor);
     font-size: $font-size !important;
     font-weight: $font-semibold;
   }
@@ -30,4 +30,4 @@ $context-menu-width: 350px;
       background-color: rgba(255, 255, 255, 0.2);
     }
   }
-}
+}

+ 6 - 6
client/src/sass/player/peertube-skin.scss

@@ -10,9 +10,8 @@
 }
 
 .video-js.vjs-peertube-skin {
-
   font-size: $font-size;
-  color: $primary-foreground-color;
+  color: var(--embedForegroundColor);
 
   .vjs-dock-text {
     padding-right: 10px;
@@ -114,7 +113,7 @@
   .vjs-control-bar,
   .vjs-big-play-button,
   .vjs-settings-dialog {
-    background-color: rgba($primary-background-color, 0.5);
+    background-color: var(--embedBigPlayBackgroundColor);
   }
 
   .vjs-poster {
@@ -139,7 +138,8 @@
     .vjs-theater-control,
     .vjs-settings
     {
-      color: $primary-foreground-color !important;
+      color: var(--embedForegroundColor) !important;
+
       opacity: $primary-foreground-opacity;
       transition: opacity .1s;
 
@@ -151,7 +151,7 @@
     .vjs-current-time,
     .vjs-duration,
     .vjs-peertube {
-      color: $primary-foreground-color;
+      color: var(--embedForegroundColor);
       opacity: $primary-foreground-opacity;
     }
 
@@ -171,7 +171,7 @@
         transition: none;
 
         .vjs-play-progress {
-          background: $primary-foreground-color;
+          background: var(--embedForegroundColor);
 
           // Not display the circle if the progress is not hovered
           &::before {

+ 1 - 1
client/src/sass/player/settings-menu.scss

@@ -38,7 +38,7 @@ $setting-transition-easing: ease-out;
     position: absolute;
     right: .5em;
     bottom: 3.5em;
-    color: $primary-foreground-color;
+    color: var(--embedForegroundColor);
     opacity: $primary-foreground-opacity;
     margin: 0 auto;
     font-size: $font-size !important;

+ 130 - 0
client/src/standalone/videos/embed-api.ts

@@ -0,0 +1,130 @@
+import './embed.scss'
+
+import * as Channel from 'jschannel'
+import { PeerTubeResolution } from '../player/definitions'
+import { PeerTubeEmbed } from './embed'
+
+/**
+ * Embed API exposes control of the embed player to the outside world via
+ * JSChannels and window.postMessage
+ */
+export class PeerTubeEmbedApi {
+  private channel: Channel.MessagingChannel
+  private isReady = false
+  private resolutions: PeerTubeResolution[] = null
+
+  constructor (private embed: PeerTubeEmbed) {
+  }
+
+  initialize () {
+    this.constructChannel()
+    this.setupStateTracking()
+
+    // We're ready!
+
+    this.notifyReady()
+  }
+
+  private get element () {
+    return this.embed.videoElement
+  }
+
+  private constructChannel () {
+    const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
+
+    channel.bind('play', (txn, params) => this.embed.player.play())
+    channel.bind('pause', (txn, params) => this.embed.player.pause())
+    channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
+    channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
+    channel.bind('getVolume', (txn, value) => this.embed.player.volume())
+    channel.bind('isReady', (txn, params) => this.isReady)
+    channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
+    channel.bind('getResolutions', (txn, params) => this.resolutions)
+    channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
+    channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
+    channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
+
+    this.channel = channel
+  }
+
+  private setResolution (resolutionId: number) {
+    if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return
+
+    // Auto resolution
+    if (resolutionId === -1) {
+      this.embed.player.webtorrent().enableAutoResolution()
+      return
+    }
+
+    this.embed.player.webtorrent().disableAutoResolution()
+    this.embed.player.webtorrent().updateResolution(resolutionId)
+  }
+
+  /**
+   * Let the host know that we're ready to go!
+   */
+  private notifyReady () {
+    this.isReady = true
+    this.channel.notify({ method: 'ready', params: true })
+  }
+
+  private setupStateTracking () {
+    let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
+
+    setInterval(() => {
+      const position = this.element.currentTime
+      const volume = this.element.volume
+
+      this.channel.notify({
+        method: 'playbackStatusUpdate',
+        params: {
+          position,
+          volume,
+          playbackState: currentState
+        }
+      })
+    }, 500)
+
+    this.element.addEventListener('play', ev => {
+      currentState = 'playing'
+      this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
+    })
+
+    this.element.addEventListener('pause', ev => {
+      currentState = 'paused'
+      this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
+    })
+
+    // PeerTube specific capabilities
+
+    if (this.embed.player.webtorrent) {
+      this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions())
+      this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
+    }
+  }
+
+  private loadWebTorrentResolutions () {
+    const resolutions = []
+    const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId()
+
+    for (const videoFile of this.embed.player.webtorrent().videoFiles) {
+      let label = videoFile.resolution.label
+      if (videoFile.fps && videoFile.fps >= 50) {
+        label += videoFile.fps
+      }
+
+      resolutions.push({
+        id: videoFile.resolution.id,
+        label,
+        src: videoFile.magnetUri,
+        active: videoFile.resolution.id === currentResolutionId
+      })
+    }
+
+    this.resolutions = resolutions
+    this.channel.notify({
+      method: 'resolutionUpdate',
+      params: this.resolutions
+    })
+  }
+}

+ 1 - 1
client/src/standalone/videos/embed.html

@@ -11,7 +11,7 @@
     <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" />
   </head>
 
-  <body>
+  <body id="custom-css">
 
     <div id="error-block">
       <h1 id="error-title"></h1>

+ 57 - 148
client/src/standalone/videos/embed.ts

@@ -1,9 +1,6 @@
 import './embed.scss'
 
-import * as Channel from 'jschannel'
-
 import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared'
-import { PeerTubeResolution } from '../player/definitions'
 import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
 import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
 import {
@@ -13,133 +10,9 @@ import {
   PlayerMode
 } from '../../assets/player/peertube-player-manager'
 import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
+import { PeerTubeEmbedApi } from './embed-api'
 
-/**
- * Embed API exposes control of the embed player to the outside world via
- * JSChannels and window.postMessage
- */
-class PeerTubeEmbedApi {
-  private channel: Channel.MessagingChannel
-  private isReady = false
-  private resolutions: PeerTubeResolution[] = null
-
-  constructor (private embed: PeerTubeEmbed) {
-  }
-
-  initialize () {
-    this.constructChannel()
-    this.setupStateTracking()
-
-    // We're ready!
-
-    this.notifyReady()
-  }
-
-  private get element () {
-    return this.embed.videoElement
-  }
-
-  private constructChannel () {
-    const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
-
-    channel.bind('play', (txn, params) => this.embed.player.play())
-    channel.bind('pause', (txn, params) => this.embed.player.pause())
-    channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
-    channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
-    channel.bind('getVolume', (txn, value) => this.embed.player.volume())
-    channel.bind('isReady', (txn, params) => this.isReady)
-    channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
-    channel.bind('getResolutions', (txn, params) => this.resolutions)
-    channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
-    channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
-    channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
-
-    this.channel = channel
-  }
-
-  private setResolution (resolutionId: number) {
-    if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return
-
-    // Auto resolution
-    if (resolutionId === -1) {
-      this.embed.player.webtorrent().enableAutoResolution()
-      return
-    }
-
-    this.embed.player.webtorrent().disableAutoResolution()
-    this.embed.player.webtorrent().updateResolution(resolutionId)
-  }
-
-  /**
-   * Let the host know that we're ready to go!
-   */
-  private notifyReady () {
-    this.isReady = true
-    this.channel.notify({ method: 'ready', params: true })
-  }
-
-  private setupStateTracking () {
-    let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
-
-    setInterval(() => {
-      const position = this.element.currentTime
-      const volume = this.element.volume
-
-      this.channel.notify({
-        method: 'playbackStatusUpdate',
-        params: {
-          position,
-          volume,
-          playbackState: currentState
-        }
-      })
-    }, 500)
-
-    this.element.addEventListener('play', ev => {
-      currentState = 'playing'
-      this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
-    })
-
-    this.element.addEventListener('pause', ev => {
-      currentState = 'paused'
-      this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
-    })
-
-    // PeerTube specific capabilities
-
-    if (this.embed.player.webtorrent) {
-      this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions())
-      this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
-    }
-  }
-
-  private loadWebTorrentResolutions () {
-    const resolutions = []
-    const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId()
-
-    for (const videoFile of this.embed.player.webtorrent().videoFiles) {
-      let label = videoFile.resolution.label
-      if (videoFile.fps && videoFile.fps >= 50) {
-        label += videoFile.fps
-      }
-
-      resolutions.push({
-        id: videoFile.resolution.id,
-        label,
-        src: videoFile.magnetUri,
-        active: videoFile.resolution.id === currentResolutionId
-      })
-    }
-
-    this.resolutions = resolutions
-    this.channel.notify({
-      method: 'resolutionUpdate',
-      params: this.resolutions
-    })
-  }
-}
-
-class PeerTubeEmbed {
+export class PeerTubeEmbed {
   videoElement: HTMLVideoElement
   player: any
   playerOptions: any
@@ -152,6 +25,12 @@ class PeerTubeEmbed {
   enableApi = false
   startTime: number | string = 0
   stopTime: number | string
+
+  title: boolean
+  warningTitle: boolean
+  bigPlayBackgroundColor: string
+  foregroundColor: string
+
   mode: PlayerMode
   scope = 'peertube'
 
@@ -245,13 +124,18 @@ class PeerTubeEmbed {
       this.controls = this.getParamToggle(params, 'controls', true)
       this.muted = this.getParamToggle(params, 'muted', false)
       this.loop = this.getParamToggle(params, 'loop', false)
+      this.title = this.getParamToggle(params, 'title', true)
       this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
+      this.warningTitle = this.getParamToggle(params, 'warningTitle', true)
 
       this.scope = this.getParamString(params, 'scope', this.scope)
       this.subtitle = this.getParamString(params, 'subtitle')
       this.startTime = this.getParamString(params, 'start')
       this.stopTime = this.getParamString(params, 'stop')
 
+      this.bigPlayBackgroundColor = this.getParamString(params, 'bigPlayBackgroundColor')
+      this.foregroundColor = this.getParamString(params, 'foregroundColor')
+
       this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent'
     } catch (err) {
       console.error('Cannot get params from URL.', err)
@@ -276,15 +160,7 @@ class PeerTubeEmbed {
     }
 
     const videoInfo: VideoDetails = await videoResponse.json()
-    let videoCaptions: VideoJSCaption[] = []
-    if (captionsResponse.ok) {
-      const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
-      videoCaptions = data.map(c => ({
-        label: peertubeTranslate(c.language.label, serverTranslations),
-        language: c.language.id,
-        src: window.location.origin + c.captionPath
-      }))
-    }
+    const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
 
     this.loadParams()
 
@@ -337,33 +213,66 @@ class PeerTubeEmbed {
     }
 
     this.player = await PeertubePlayerManager.initialize(this.mode, options)
-
     this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
 
     window[ 'videojsPlayer' ] = this.player
 
+    this.buildCSS()
+
+    await this.buildDock(videoInfo, configResponse)
+
+    this.initializeApi()
+  }
+
+  private handleError (err: Error, translations?: { [ id: string ]: string }) {
+    if (err.message.indexOf('from xs param') !== -1) {
+      this.player.dispose()
+      this.videoElement = null
+      this.displayError('This video is not available because the remote instance is not responding.', translations)
+      return
+    }
+  }
+
+  private async buildDock (videoInfo: VideoDetails, configResponse: Response) {
     if (this.controls) {
+      const title = this.title ? videoInfo.name : undefined
+
       const config: ServerConfig = await configResponse.json()
-      const description = config.tracker.enabled
+      const description = config.tracker.enabled && this.warningTitle
         ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
         : undefined
 
       this.player.dock({
-        title: videoInfo.name,
+        title,
         description
       })
     }
+  }
 
-    this.initializeApi()
+  private buildCSS () {
+    const body = document.getElementById('custom-css')
+
+    if (this.bigPlayBackgroundColor) {
+      body.style.setProperty('--embedBigPlayBackgroundColor', this.bigPlayBackgroundColor)
+    }
+
+    if (this.foregroundColor) {
+      body.style.setProperty('--embedForegroundColor', this.foregroundColor)
+    }
   }
 
-  private handleError (err: Error, translations?: { [ id: string ]: string }) {
-    if (err.message.indexOf('from xs param') !== -1) {
-      this.player.dispose()
-      this.videoElement = null
-      this.displayError('This video is not available because the remote instance is not responding.', translations)
-      return
+  private async buildCaptions (serverTranslations: any, captionsResponse: Response): Promise<VideoJSCaption[]> {
+    if (captionsResponse.ok) {
+      const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
+
+      return data.map(c => ({
+        label: peertubeTranslate(c.language.label, serverTranslations),
+        language: c.language.id,
+        src: window.location.origin + c.captionPath
+      }))
     }
+
+    return []
   }
 }
 

+ 1 - 2
server/tests/api/check-params/users.ts

@@ -379,8 +379,7 @@ describe('Test users API validators', function () {
     it('Should succeed without password change with the correct params', async function () {
       const fields = {
         nsfwPolicy: 'blur',
-        autoPlayVideo: false,
-        email: 'super_email@example.com'
+        autoPlayVideo: false
       }
 
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 })

+ 2 - 1
server/tests/api/users/users-verification.ts

@@ -109,7 +109,8 @@ describe('Test users account verification', function () {
       await updateMyUser({
         url: server.url,
         accessToken: userAccessToken,
-        email: 'updated@example.com'
+        email: 'updated@example.com',
+        currentPassword: user1.password
       })
 
       await waitJobs(server)