From 6aa541481390980f9c85d2e66514ba0e6ce77a35 Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Tue, 24 Sep 2019 08:48:01 +0200 Subject: [PATCH] Autoplay next recommended video (#2137) * Start working on autoplay of next video * Ignore changes made by gitpod * Apply changes from PR#1370 * Correct the spelling of recommendations * Fix linting errors * Move boolean check to existing onEnded handler * Pick a random video until the recommendations are improved * Add simple tests for autoPlayNextVideo * Fix lint ...again --- .gitignore | 2 ++ .../my-account-video-settings.component.html | 5 +++++ .../my-account-video-settings.component.ts | 4 ++++ client/src/app/shared/users/user.model.ts | 1 + .../+video-watch/video-watch.component.html | 6 +++++- .../+video-watch/video-watch.component.ts | 17 +++++++++++++++++ .../recommended-videos.component.ts | 4 +++- server/controllers/api/users/me.ts | 1 + server/helpers/custom-validators/users.ts | 5 +++++ server/models/account/user.ts | 8 ++++++++ server/tests/api/check-params/users.ts | 8 ++++++++ server/tests/api/users/users.ts | 13 +++++++++++++ shared/models/users/user-update-me.model.ts | 1 + shared/models/users/user.model.ts | 1 + 14 files changed, 74 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3a91facb4..fbf8fdf3c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,8 @@ /scripts/i18n/generate-iso639-target.ts # Other +/dump.rdb +/.theia/ /profiling/ /*.zip /*.tar.xz diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html index a11238925..06fd9833a 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html @@ -47,6 +47,11 @@ inputName="autoPlayVideo" formControlName="autoPlayVideo" i18n-labelText labelText="Automatically plays video" > + + diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts index 4fb828082..99eee23b8 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts @@ -36,6 +36,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI nsfwPolicy: null, webTorrentEnabled: null, autoPlayVideo: null, + autoPlayNextVideo: null, videoLanguages: null }) @@ -57,6 +58,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI nsfwPolicy: this.user.nsfwPolicy, webTorrentEnabled: this.user.webTorrentEnabled, autoPlayVideo: this.user.autoPlayVideo === true, + autoPlayNextVideo: this.user.autoPlayNextVideo, videoLanguages }) }) @@ -66,6 +68,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] const webTorrentEnabled = this.form.value['webTorrentEnabled'] const autoPlayVideo = this.form.value['autoPlayVideo'] + const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] let videoLanguages: string[] = this.form.value['videoLanguages'] if (Array.isArray(videoLanguages)) { @@ -84,6 +87,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI nsfwPolicy, webTorrentEnabled, autoPlayVideo, + autoPlayNextVideo, videoLanguages } diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 656b73dd2..e0b3f1faf 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts @@ -16,6 +16,7 @@ export class User implements UserServerModel { adminFlags?: UserAdminFlag autoPlayVideo: boolean + autoPlayNextVideo: boolean webTorrentEnabled: boolean videosHistoryEnabled: boolean videoLanguages: string[] 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 6a02f630a..cd60c407f 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html @@ -199,7 +199,11 @@ - +
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 21a24113f..1e7991738 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -35,6 +35,7 @@ import { getStoredTheater } from '../../../assets/player/peertube-player-local-s 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' @Component({ selector: 'my-video-watch', @@ -69,6 +70,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { remoteServerDown = false hotkeys: Hotkey[] + private nextVideoUuid = '' private currentTime: number private paramsSub: Subscription private queryParamsSub: Subscription @@ -217,6 +219,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { return this.video.tags } + onRecommendations (videos: Video[]) { + if (videos.length > 0) { + // Pick a random video until the recommendations are improved + this.nextVideoUuid = videos[randomInt(0,videos.length - 1)].uuid + } + } + onVideoRemoved () { this.redirectService.redirectToHomepage() } @@ -477,6 +486,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.player.one('ended', () => { if (this.playlist) { this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) + } else if (this.user && this.user.autoPlayNextVideo) { + this.zone.run(() => this.autoplayNext()) } }) @@ -500,6 +511,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.hooks.runAction('action:video-watch.video.loaded', 'video-watch') } + private autoplayNext () { + if (this.nextVideoUuid) { + this.router.navigate([ '/videos/watch', this.nextVideoUuid ]) + } + } + private setRating (nextRating: UserVideoRateType) { const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable } = { like: this.videoService.setVideoLike, diff --git a/client/src/app/videos/recommendations/recommended-videos.component.ts b/client/src/app/videos/recommendations/recommended-videos.component.ts index 68fd750cc..7e0fb8856 100644 --- a/client/src/app/videos/recommendations/recommended-videos.component.ts +++ b/client/src/app/videos/recommendations/recommended-videos.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges } from '@angular/core' +import { Component, Input, Output, OnChanges, EventEmitter } from '@angular/core' import { Observable } from 'rxjs' import { Video } from '@app/shared/video/video.model' import { RecommendationInfo } from '@app/shared/video/recommendation-info.model' @@ -12,6 +12,7 @@ import { User } from '@app/shared' export class RecommendedVideosComponent implements OnChanges { @Input() inputRecommendation: RecommendationInfo @Input() user: User + @Output() gotRecommendations = new EventEmitter() readonly hasVideos$: Observable readonly videos$: Observable @@ -21,6 +22,7 @@ export class RecommendedVideosComponent implements OnChanges { ) { this.videos$ = this.store.recommendations$ this.hasVideos$ = this.store.hasRecommendations$ + this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) } public ngOnChanges (): void { diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index bf872ca52..cfc346c35 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts @@ -175,6 +175,7 @@ async function updateMe (req: express.Request, res: express.Response) { if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy 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.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 68e84d9eb..16a95f120 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts @@ -65,6 +65,10 @@ function isUserBlockedValid (value: any) { return isBooleanValid(value) } +function isUserAutoPlayNextVideoValid (value: any) { + return isBooleanValid(value) +} + function isNoInstanceConfigWarningModal (value: any) { return isBooleanValid(value) } @@ -106,6 +110,7 @@ export { isUserNSFWPolicyValid, isUserWebTorrentEnabledValid, isUserAutoPlayVideoValid, + isUserAutoPlayNextVideoValid, isUserDisplayNameValid, isUserDescriptionValid, isNoInstanceConfigWarningModal, diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 451e1fd6b..38c6d474a 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -25,6 +25,7 @@ import { isNoInstanceConfigWarningModal, isUserAdminFlagsValid, isUserAutoPlayVideoValid, + isUserAutoPlayNextVideoValid, isUserBlockedReasonValid, isUserBlockedValid, isUserEmailVerifiedValid, @@ -160,6 +161,12 @@ export class UserModel extends Model { @Column autoPlayVideo: boolean + @AllowNull(false) + @Default(false) + @Is('UserAutoPlayNextVideo', value => throwIfNotValid(value, isUserAutoPlayNextVideoValid, 'auto play next video boolean')) + @Column + autoPlayNextVideo: boolean + @AllowNull(true) @Default(null) @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages')) @@ -597,6 +604,7 @@ export class UserModel extends Model { webTorrentEnabled: this.webTorrentEnabled, videosHistoryEnabled: this.videosHistoryEnabled, autoPlayVideo: this.autoPlayVideo, + autoPlayNextVideo: this.autoPlayNextVideo, videoLanguages: this.videoLanguages, role: this.role, diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 9d7ff8984..5d5af284c 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts @@ -418,6 +418,14 @@ describe('Test users API validators', function () { await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) }) + it('Should fail with an invalid autoPlayNextVideo attribute', async function () { + const fields = { + autoPlayNextVideo: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) + }) + it('Should fail with an invalid videosHistoryEnabled attribute', async function () { const fields = { videosHistoryEnabled: -1 diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 95b1bb626..ca06942e7 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts @@ -481,6 +481,19 @@ describe('Test users', function () { expect(user.autoPlayVideo).to.be.false }) + it('Should be able to change the autoPlayNextVideo attribute', async function () { + await updateMyUser({ + url: server.url, + accessToken: accessTokenUser, + autoPlayNextVideo: true + }) + + const res = await getMyUserInformation(server.url, accessTokenUser) + const user = res.body + + expect(user.autoPlayNextVideo).to.be.true + }) + it('Should be able to change the email attribute', async function () { await updateMyUser({ url: server.url, diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index 99b9a65bd..0a833f84c 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts @@ -7,6 +7,7 @@ export interface UserUpdateMe { webTorrentEnabled?: boolean autoPlayVideo?: boolean + autoPlayNextVideo?: boolean videosHistoryEnabled?: boolean videoLanguages?: string[] diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index f67d262b0..1ca8ddcba 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts @@ -17,6 +17,7 @@ export interface User { adminFlags?: UserAdminFlag autoPlayVideo: boolean + autoPlayNextVideo: boolean webTorrentEnabled: boolean videosHistoryEnabled: boolean videoLanguages: string[]