From bee29df8a9ba3090be3daa8ff806dd9a26d7a5cf Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Wed, 11 Dec 2019 20:20:42 +0100 Subject: [PATCH] autoplay next video support for playlists --- .../shared/images/global-icon.component.scss | 8 ++-- client/src/app/shared/users/user.model.ts | 1 + ...-playlist-element-miniature.component.scss | 4 -- .../video-watch-playlist.component.html | 11 +++++ .../video-watch-playlist.component.scss | 15 ++++++ .../video-watch-playlist.component.ts | 48 ++++++++++++++++++- .../+video-watch/video-watch.component.html | 1 + .../+video-watch/video-watch.component.ts | 13 +++-- .../videos/+video-watch/video-watch.module.ts | 4 +- .../recommended-videos.component.html | 8 ++-- .../recommended-videos.component.ts | 4 +- server/controllers/api/users/me.ts | 1 + server/helpers/custom-validators/users.ts | 5 ++ server/initializers/constants.ts | 2 +- .../migrations/0460-user-playlist-autoplay.ts | 27 +++++++++++ server/models/account/user.ts | 8 ++++ shared/models/users/user-update-me.model.ts | 1 + shared/models/users/user.model.ts | 1 + 18 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 server/initializers/migrations/0460-user-playlist-autoplay.ts diff --git a/client/src/app/shared/images/global-icon.component.scss b/client/src/app/shared/images/global-icon.component.scss index 32fb9badb..6795d6628 100644 --- a/client/src/app/shared/images/global-icon.component.scss +++ b/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; + } } diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index e0b3f1faf..7707d7dda 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/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[] diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss index b55ca0dea..4d9d249d9 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss +++ b/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 { diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.html b/client/src/app/videos/+video-watch/video-watch-playlist.component.html index c89936bd1..c07ba1ed6 100644 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.html +++ b/client/src/app/videos/+video-watch/video-watch-playlist.component.html @@ -14,6 +14,17 @@ {{ currentPlaylistPosition }}{{ playlistPagination.totalItems }} + +
+ +
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss index 4c24d6b05..ba8d1c3e1 100644 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss +++ b/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 { diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts index 524055ce2..ed2aeda6e 100644 --- a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts +++ b/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') + }) + } } diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index c57b00032..97f3a336e 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -217,6 +217,7 @@
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 156a3235a..f13acec40 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/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()) } }) diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts index f083aca4d..2e45e5674 100644 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ b/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: [ diff --git a/client/src/app/videos/recommendations/recommended-videos.component.html b/client/src/app/videos/recommendations/recommended-videos.component.html index 5f223078a..c82642c1c 100644 --- a/client/src/app/videos/recommendations/recommended-videos.component.html +++ b/client/src/app/videos/recommendations/recommended-videos.component.html @@ -4,15 +4,17 @@
Other videos
-
- Autoplay +
+ Autoplay
-
+
+ +
diff --git a/client/src/app/videos/recommendations/recommended-videos.component.ts b/client/src/app/videos/recommendations/recommended-videos.component.ts index 4c3cde225..771ae54a2 100644 --- a/client/src/app/videos/recommendations/recommended-videos.component.ts +++ b/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() readonly hasVideos$: Observable diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index cfc346c35..ba976ab03 100644 --- a/server/controllers/api/users/me.ts +++ b/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 diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 16a95f120..008916a04 100644 --- a/server/helpers/custom-validators/users.ts +++ b/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, diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index d0cf4d5de..00238f7a1 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 455 +const LAST_MIGRATION_VERSION = 460 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0460-user-playlist-autoplay.ts b/server/initializers/migrations/0460-user-playlist-autoplay.ts new file mode 100644 index 000000000..3067ac1a4 --- /dev/null +++ b/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 { + { + 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 +} diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 522ea3310..b2cd25bc3 100644 --- a/server/models/account/user.ts +++ b/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 { @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 { videosHistoryEnabled: this.videosHistoryEnabled, autoPlayVideo: this.autoPlayVideo, autoPlayNextVideo: this.autoPlayNextVideo, + autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist, videoLanguages: this.videoLanguages, role: this.role, diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index 0a833f84c..db6539cd2 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/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[] diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index 1ca8ddcba..90d59ac56 100644 --- a/shared/models/users/user.model.ts +++ b/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[]