Browse Source

autoplay next video support for playlists

Rigel Kent 4 years ago
parent
commit
bee29df8a9

+ 5 - 3
client/src/app/shared/images/global-icon.component.scss

@@ -1,4 +1,6 @@
-::ng-deep svg {
-  width: inherit;
-  height: inherit;
+::ng-deep {
+  svg {
+    width: inherit;
+    height: inherit;
+  }
 }

+ 1 - 0
client/src/app/shared/users/user.model.ts

@@ -17,6 +17,7 @@ export class User implements UserServerModel {
 
   autoPlayVideo: boolean
   autoPlayNextVideo: boolean
+  autoPlayNextVideoPlaylist: boolean
   webTorrentEnabled: boolean
   videosHistoryEnabled: boolean
   videoLanguages: string[]

+ 0 - 4
client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss

@@ -72,10 +72,6 @@ my-video-thumbnail,
 
       a {
         width: auto;
-
-        &:hover {
-          text-decoration: underline !important;
-        }
       }
 
       .video-info-account, .video-info-timestamp {

+ 11 - 0
client/src/app/videos/+video-watch/video-watch-playlist.component.html

@@ -14,6 +14,17 @@
         <span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
       </div>
     </div>
+
+    <div class="playlist-controls">
+      <my-global-icon
+        iconName="videos"
+        [class.active]="autoPlayNextVideoPlaylist"
+        (click)="switchAutoPlayNextVideoPlaylist()"
+        [ngbTooltip]="autoPlayNextVideoPlaylistSwitchText"
+        placement="bottom auto"
+        container="body"
+      ></my-global-icon>
+    </div>
   </div>
 
   <div *ngFor="let playlistElement of playlistElements">

+ 15 - 0
client/src/app/videos/+video-watch/video-watch-playlist.component.scss

@@ -34,6 +34,21 @@
         margin: 0 3px;
       }
     }
+
+    .playlist-controls {
+      display: flex;
+      margin: 10px 0;
+
+      my-global-icon {
+        &:not(.active) {
+          opacity: .5
+        }
+
+        ::ng-deep {
+          cursor: pointer;
+        }
+      }
+    }
   }
 
   my-video-playlist-element-miniature {

+ 46 - 2
client/src/app/videos/+video-watch/video-watch-playlist.component.ts

@@ -3,9 +3,12 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
 import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models'
 import { Router } from '@angular/router'
-import { AuthService } from '@app/core'
+import { User, UserService } from '@app/shared'
+import { AuthService, Notifier } from '@app/core'
 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
 import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
+import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
+import { I18n } from '@ngx-translate/i18n-polyfill'
 
 @Component({
   selector: 'my-video-watch-playlist',
@@ -13,6 +16,8 @@ import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-
   styleUrls: [ './video-watch-playlist.component.scss' ]
 })
 export class VideoWatchPlaylistComponent {
+  static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST = 'auto_play_video_playlist'
+
   @Input() video: VideoDetails
   @Input() playlist: VideoPlaylist
 
@@ -23,14 +28,24 @@ export class VideoWatchPlaylistComponent {
     totalItems: null
   }
 
+  autoPlayNextVideoPlaylist: boolean
+  autoPlayNextVideoPlaylistSwitchText = ''
   noPlaylistVideos = false
   currentPlaylistPosition = 1
 
   constructor (
+    private userService: UserService,
     private auth: AuthService,
+    private notifier: Notifier,
+    private i18n: I18n,
     private videoPlaylist: VideoPlaylistService,
     private router: Router
-  ) {}
+  ) {
+    this.autoPlayNextVideoPlaylist = this.auth.isLoggedIn()
+      ? this.auth.getUser().autoPlayNextVideoPlaylist
+      : peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) !== 'false'
+    this.setAutoPlayNextVideoPlaylistSwitchText()
+  }
 
   onPlaylistVideosNearOfBottom () {
     // Last page
@@ -121,4 +136,33 @@ export class VideoWatchPlaylistComponent {
       this.router.navigate([],{ queryParams: { videoId: next.video.uuid, start, stop } })
     }
   }
+
+  switchAutoPlayNextVideoPlaylist () {
+    this.autoPlayNextVideoPlaylist = !this.autoPlayNextVideoPlaylist
+    this.setAutoPlayNextVideoPlaylistSwitchText()
+
+    peertubeLocalStorage.setItem(
+      VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST,
+      this.autoPlayNextVideoPlaylist.toString()
+    )
+
+    if (this.auth.isLoggedIn()) {
+      const details = {
+        autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist
+      }
+
+      this.userService.updateMyProfile(details).subscribe(
+        () => {
+          this.auth.refreshUserInformation()
+        },
+        err => this.notifier.error(err.message)
+      )
+    }
+  }
+
+  private setAutoPlayNextVideoPlaylistSwitchText () {
+    this.autoPlayNextVideoPlaylistSwitchText = this.i18n('{{verb}} autoplay for playlists', {
+      verb: this.autoPlayNextVideoPlaylist ? this.i18n('Disable') : this.i18n('Enable')
+    })
+  }
 }

+ 1 - 0
client/src/app/videos/+video-watch/video-watch.component.html

@@ -217,6 +217,7 @@
     <my-recommended-videos
         [inputRecommendation]="{ uuid: video.uuid, tags: video.tags }"
         [user]="user"
+        [playlist]="playlist"
         (gotRecommendations)="onRecommendations($event)"
     ></my-recommended-videos>
   </div>

+ 10 - 3
client/src/app/videos/+video-watch/video-watch.component.ts

@@ -37,6 +37,7 @@ import { PluginService } from '@app/core/plugins/plugin.service'
 import { HooksService } from '@app/core/plugins/hooks.service'
 import { PlatformLocation } from '@angular/common'
 import { randomInt } from '@shared/core-utils/miscs/miscs'
+import { RecommendedVideosComponent } from '../recommendations/recommended-videos.component'
 
 @Component({
   selector: 'my-video-watch',
@@ -436,10 +437,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
       this.player.one('ended', () => {
         if (this.playlist) {
-          this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
+          if (
+            this.user && this.user.autoPlayNextVideoPlaylist ||
+            peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
+          ) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
         } else if (
           this.user && this.user.autoPlayNextVideo ||
-          peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
+          peertubeLocalStorage.getItem(RecommendedVideosComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
         ) {
           this.zone.run(() => this.autoplayNext())
         }
@@ -447,7 +451,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
       this.player.one('stopped', () => {
         if (this.playlist) {
-          this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
+          if (
+            this.user && this.user.autoPlayNextVideoPlaylist ||
+            peertubeLocalStorage.getItem(VideoWatchPlaylistComponent.LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO_PLAYLIST) === 'true'
+          ) this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
         }
       })
 

+ 3 - 1
client/src/app/videos/+video-watch/video-watch.module.ts

@@ -12,6 +12,7 @@ import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
 import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
 import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
 import { QRCodeModule } from 'angularx-qrcode'
+import { InputSwitchModule } from 'primeng/inputswitch'
 
 @NgModule({
   imports: [
@@ -19,7 +20,8 @@ import { QRCodeModule } from 'angularx-qrcode'
     SharedModule,
     NgbTooltipModule,
     QRCodeModule,
-    RecommendationsModule
+    RecommendationsModule,
+    InputSwitchModule
   ],
 
   declarations: [

+ 5 - 3
client/src/app/videos/recommendations/recommended-videos.component.html

@@ -4,15 +4,17 @@
       <div i18n class="title-page title-page-single">
         Other videos
       </div>
-      <div class="d-flex title-page-autoplay">
-        <span>Autoplay</span>
+      <div *ngIf="!playlist" class="d-flex title-page-autoplay">
+        <span i18n>Autoplay</span>
         <p-inputSwitch [(ngModel)]="autoPlayNextVideo" (ngModelChange)="switchAutoPlayNextVideo()"></p-inputSwitch>
       </div>
     </div>
 
-    <div *ngFor="let video of (videos$ | async)">
+    <div *ngFor="let video of (videos$ | async); let i = index; let length = count">
       <my-video-miniature [video]="video" [user]="user" (videoBlacklisted)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()">
       </my-video-miniature>
+      
+      <hr *ngIf="!playlist && i == 0 && length > 1" />
     </div>
   </ng-container>
 </div>

+ 3 - 1
client/src/app/videos/recommendations/recommended-videos.component.ts

@@ -1,6 +1,7 @@
 import { Component, Input, Output, OnChanges, EventEmitter } from '@angular/core'
 import { Observable } from 'rxjs'
 import { Video } from '@app/shared/video/video.model'
+import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { RecommendationInfo } from '@app/shared/video/recommendation-info.model'
 import { RecommendedVideosStore } from '@app/videos/recommendations/recommended-videos.store'
 import { User } from '@app/shared'
@@ -14,10 +15,11 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
   styleUrls: [ './recommended-videos.component.scss' ]
 })
 export class RecommendedVideosComponent implements OnChanges {
-  private static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
+  static LOCAL_STORAGE_AUTO_PLAY_NEXT_VIDEO = 'auto_play_next_video'
 
   @Input() inputRecommendation: RecommendationInfo
   @Input() user: User
+  @Input() playlist: VideoPlaylist
   @Output() gotRecommendations = new EventEmitter<Video[]>()
 
   readonly hasVideos$: Observable<boolean>

+ 1 - 0
server/controllers/api/users/me.ts

@@ -176,6 +176,7 @@ async function updateMe (req: express.Request, res: express.Response) {
   if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
   if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
   if (body.autoPlayNextVideo !== undefined) user.autoPlayNextVideo = body.autoPlayNextVideo
+  if (body.autoPlayNextVideoPlaylist !== undefined) user.autoPlayNextVideoPlaylist = body.autoPlayNextVideoPlaylist
   if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
   if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages
   if (body.theme !== undefined) user.theme = body.theme

+ 5 - 0
server/helpers/custom-validators/users.ts

@@ -69,6 +69,10 @@ function isUserAutoPlayNextVideoValid (value: any) {
   return isBooleanValid(value)
 }
 
+function isUserAutoPlayNextVideoPlaylistValid (value: any) {
+  return isBooleanValid(value)
+}
+
 function isNoInstanceConfigWarningModal (value: any) {
   return isBooleanValid(value)
 }
@@ -111,6 +115,7 @@ export {
   isUserWebTorrentEnabledValid,
   isUserAutoPlayVideoValid,
   isUserAutoPlayNextVideoValid,
+  isUserAutoPlayNextVideoPlaylistValid,
   isUserDisplayNameValid,
   isUserDescriptionValid,
   isNoInstanceConfigWarningModal,

+ 1 - 1
server/initializers/constants.ts

@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 455
+const LAST_MIGRATION_VERSION = 460
 
 // ---------------------------------------------------------------------------
 

+ 27 - 0
server/initializers/migrations/0460-user-playlist-autoplay.ts

@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  {
+    const data = {
+      type: Sequelize.BOOLEAN,
+      allowNull: false,
+      defaultValue: true
+    }
+
+    await utils.queryInterface.addColumn('user', 'autoPlayNextVideoPlaylist', data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}

+ 8 - 0
server/models/account/user.ts

@@ -26,6 +26,7 @@ import {
   isUserAdminFlagsValid,
   isUserAutoPlayVideoValid,
   isUserAutoPlayNextVideoValid,
+  isUserAutoPlayNextVideoPlaylistValid,
   isUserBlockedReasonValid,
   isUserBlockedValid,
   isUserEmailVerifiedValid,
@@ -167,6 +168,12 @@ export class UserModel extends Model<UserModel> {
   @Column
   autoPlayNextVideo: boolean
 
+  @AllowNull(false)
+  @Default(true)
+  @Is('UserAutoPlayNextVideoPlaylist', value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean'))
+  @Column
+  autoPlayNextVideoPlaylist: boolean
+
   @AllowNull(true)
   @Default(null)
   @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages'))
@@ -619,6 +626,7 @@ export class UserModel extends Model<UserModel> {
       videosHistoryEnabled: this.videosHistoryEnabled,
       autoPlayVideo: this.autoPlayVideo,
       autoPlayNextVideo: this.autoPlayNextVideo,
+      autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist,
       videoLanguages: this.videoLanguages,
 
       role: this.role,

+ 1 - 0
shared/models/users/user-update-me.model.ts

@@ -8,6 +8,7 @@ export interface UserUpdateMe {
   webTorrentEnabled?: boolean
   autoPlayVideo?: boolean
   autoPlayNextVideo?: boolean
+  autoPlayNextVideoPlaylist?: boolean
   videosHistoryEnabled?: boolean
   videoLanguages?: string[]
 

+ 1 - 0
shared/models/users/user.model.ts

@@ -18,6 +18,7 @@ export interface User {
 
   autoPlayVideo: boolean
   autoPlayNextVideo: boolean
+  autoPlayNextVideoPlaylist: boolean
   webTorrentEnabled: boolean
   videosHistoryEnabled: boolean
   videoLanguages: string[]