diff options
author | LoveIsGrief <LoveIsGrief@users.noreply.github.com> | 2019-09-24 08:48:01 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-09-24 08:48:01 +0200 |
commit | 6aa541481390980f9c85d2e66514ba0e6ce77a35 (patch) | |
tree | 7b2e934647a732645c69e5d05e4b18152b50a6b8 | |
parent | 32d7f2b754b8d20bf44ae2121c79570cbff973c3 (diff) | |
download | PeerTube-6aa541481390980f9c85d2e66514ba0e6ce77a35.tar.gz PeerTube-6aa541481390980f9c85d2e66514ba0e6ce77a35.tar.zst PeerTube-6aa541481390980f9c85d2e66514ba0e6ce77a35.zip |
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
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 @@ | |||
37 | /scripts/i18n/generate-iso639-target.ts | 37 | /scripts/i18n/generate-iso639-target.ts |
38 | 38 | ||
39 | # Other | 39 | # Other |
40 | /dump.rdb | ||
41 | /.theia/ | ||
40 | /profiling/ | 42 | /profiling/ |
41 | /*.zip | 43 | /*.zip |
42 | /*.tar.xz | 44 | /*.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 @@ | |||
47 | inputName="autoPlayVideo" formControlName="autoPlayVideo" | 47 | inputName="autoPlayVideo" formControlName="autoPlayVideo" |
48 | i18n-labelText labelText="Automatically plays video" | 48 | i18n-labelText labelText="Automatically plays video" |
49 | ></my-peertube-checkbox> | 49 | ></my-peertube-checkbox> |
50 | |||
51 | <my-peertube-checkbox | ||
52 | inputName="autoPlayNextVideo" formControlName="autoPlayNextVideo" | ||
53 | i18n-labelText labelText="Automatically starts playing next video" | ||
54 | ></my-peertube-checkbox> | ||
50 | </div> | 55 | </div> |
51 | 56 | ||
52 | <input type="submit" i18n-value value="Save" [disabled]="!form.valid"> | 57 | <input type="submit" i18n-value value="Save" [disabled]="!form.valid"> |
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 | |||
36 | nsfwPolicy: null, | 36 | nsfwPolicy: null, |
37 | webTorrentEnabled: null, | 37 | webTorrentEnabled: null, |
38 | autoPlayVideo: null, | 38 | autoPlayVideo: null, |
39 | autoPlayNextVideo: null, | ||
39 | videoLanguages: null | 40 | videoLanguages: null |
40 | }) | 41 | }) |
41 | 42 | ||
@@ -57,6 +58,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
57 | nsfwPolicy: this.user.nsfwPolicy, | 58 | nsfwPolicy: this.user.nsfwPolicy, |
58 | webTorrentEnabled: this.user.webTorrentEnabled, | 59 | webTorrentEnabled: this.user.webTorrentEnabled, |
59 | autoPlayVideo: this.user.autoPlayVideo === true, | 60 | autoPlayVideo: this.user.autoPlayVideo === true, |
61 | autoPlayNextVideo: this.user.autoPlayNextVideo, | ||
60 | videoLanguages | 62 | videoLanguages |
61 | }) | 63 | }) |
62 | }) | 64 | }) |
@@ -66,6 +68,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
66 | const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] | 68 | const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] |
67 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] | 69 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] |
68 | const autoPlayVideo = this.form.value['autoPlayVideo'] | 70 | const autoPlayVideo = this.form.value['autoPlayVideo'] |
71 | const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] | ||
69 | 72 | ||
70 | let videoLanguages: string[] = this.form.value['videoLanguages'] | 73 | let videoLanguages: string[] = this.form.value['videoLanguages'] |
71 | if (Array.isArray(videoLanguages)) { | 74 | if (Array.isArray(videoLanguages)) { |
@@ -84,6 +87,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
84 | nsfwPolicy, | 87 | nsfwPolicy, |
85 | webTorrentEnabled, | 88 | webTorrentEnabled, |
86 | autoPlayVideo, | 89 | autoPlayVideo, |
90 | autoPlayNextVideo, | ||
87 | videoLanguages | 91 | videoLanguages |
88 | } | 92 | } |
89 | 93 | ||
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 { | |||
16 | adminFlags?: UserAdminFlag | 16 | adminFlags?: UserAdminFlag |
17 | 17 | ||
18 | autoPlayVideo: boolean | 18 | autoPlayVideo: boolean |
19 | autoPlayNextVideo: boolean | ||
19 | webTorrentEnabled: boolean | 20 | webTorrentEnabled: boolean |
20 | videosHistoryEnabled: boolean | 21 | videosHistoryEnabled: boolean |
21 | videoLanguages: string[] | 22 | 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 @@ | |||
199 | <my-video-comments [video]="video" [user]="user"></my-video-comments> | 199 | <my-video-comments [video]="video" [user]="user"></my-video-comments> |
200 | </div> | 200 | </div> |
201 | 201 | ||
202 | <my-recommended-videos [inputRecommendation]="{ uuid: video.uuid, tags: video.tags }" [user]="user"></my-recommended-videos> | 202 | <my-recommended-videos |
203 | [inputRecommendation]="{ uuid: video.uuid, tags: video.tags }" | ||
204 | [user]="user" | ||
205 | (gotRecommendations)="onRecommendations($event)" | ||
206 | ></my-recommended-videos> | ||
203 | </div> | 207 | </div> |
204 | 208 | ||
205 | <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> | 209 | <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> |
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 | |||
35 | import { PluginService } from '@app/core/plugins/plugin.service' | 35 | import { PluginService } from '@app/core/plugins/plugin.service' |
36 | import { HooksService } from '@app/core/plugins/hooks.service' | 36 | import { HooksService } from '@app/core/plugins/hooks.service' |
37 | import { PlatformLocation } from '@angular/common' | 37 | import { PlatformLocation } from '@angular/common' |
38 | import { randomInt } from '@shared/core-utils/miscs/miscs' | ||
38 | 39 | ||
39 | @Component({ | 40 | @Component({ |
40 | selector: 'my-video-watch', | 41 | selector: 'my-video-watch', |
@@ -69,6 +70,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
69 | remoteServerDown = false | 70 | remoteServerDown = false |
70 | hotkeys: Hotkey[] | 71 | hotkeys: Hotkey[] |
71 | 72 | ||
73 | private nextVideoUuid = '' | ||
72 | private currentTime: number | 74 | private currentTime: number |
73 | private paramsSub: Subscription | 75 | private paramsSub: Subscription |
74 | private queryParamsSub: Subscription | 76 | private queryParamsSub: Subscription |
@@ -217,6 +219,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
217 | return this.video.tags | 219 | return this.video.tags |
218 | } | 220 | } |
219 | 221 | ||
222 | onRecommendations (videos: Video[]) { | ||
223 | if (videos.length > 0) { | ||
224 | // Pick a random video until the recommendations are improved | ||
225 | this.nextVideoUuid = videos[randomInt(0,videos.length - 1)].uuid | ||
226 | } | ||
227 | } | ||
228 | |||
220 | onVideoRemoved () { | 229 | onVideoRemoved () { |
221 | this.redirectService.redirectToHomepage() | 230 | this.redirectService.redirectToHomepage() |
222 | } | 231 | } |
@@ -477,6 +486,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
477 | this.player.one('ended', () => { | 486 | this.player.one('ended', () => { |
478 | if (this.playlist) { | 487 | if (this.playlist) { |
479 | this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) | 488 | this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) |
489 | } else if (this.user && this.user.autoPlayNextVideo) { | ||
490 | this.zone.run(() => this.autoplayNext()) | ||
480 | } | 491 | } |
481 | }) | 492 | }) |
482 | 493 | ||
@@ -500,6 +511,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
500 | this.hooks.runAction('action:video-watch.video.loaded', 'video-watch') | 511 | this.hooks.runAction('action:video-watch.video.loaded', 'video-watch') |
501 | } | 512 | } |
502 | 513 | ||
514 | private autoplayNext () { | ||
515 | if (this.nextVideoUuid) { | ||
516 | this.router.navigate([ '/videos/watch', this.nextVideoUuid ]) | ||
517 | } | ||
518 | } | ||
519 | |||
503 | private setRating (nextRating: UserVideoRateType) { | 520 | private setRating (nextRating: UserVideoRateType) { |
504 | const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = { | 521 | const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = { |
505 | like: this.videoService.setVideoLike, | 522 | 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 @@ | |||
1 | import { Component, Input, OnChanges } from '@angular/core' | 1 | import { Component, Input, Output, OnChanges, EventEmitter } from '@angular/core' |
2 | import { Observable } from 'rxjs' | 2 | import { Observable } from 'rxjs' |
3 | import { Video } from '@app/shared/video/video.model' | 3 | import { Video } from '@app/shared/video/video.model' |
4 | import { RecommendationInfo } from '@app/shared/video/recommendation-info.model' | 4 | import { RecommendationInfo } from '@app/shared/video/recommendation-info.model' |
@@ -12,6 +12,7 @@ import { User } from '@app/shared' | |||
12 | export class RecommendedVideosComponent implements OnChanges { | 12 | export class RecommendedVideosComponent implements OnChanges { |
13 | @Input() inputRecommendation: RecommendationInfo | 13 | @Input() inputRecommendation: RecommendationInfo |
14 | @Input() user: User | 14 | @Input() user: User |
15 | @Output() gotRecommendations = new EventEmitter<Video[]>() | ||
15 | 16 | ||
16 | readonly hasVideos$: Observable<boolean> | 17 | readonly hasVideos$: Observable<boolean> |
17 | readonly videos$: Observable<Video[]> | 18 | readonly videos$: Observable<Video[]> |
@@ -21,6 +22,7 @@ export class RecommendedVideosComponent implements OnChanges { | |||
21 | ) { | 22 | ) { |
22 | this.videos$ = this.store.recommendations$ | 23 | this.videos$ = this.store.recommendations$ |
23 | this.hasVideos$ = this.store.hasRecommendations$ | 24 | this.hasVideos$ = this.store.hasRecommendations$ |
25 | this.videos$.subscribe(videos => this.gotRecommendations.emit(videos)) | ||
24 | } | 26 | } |
25 | 27 | ||
26 | public ngOnChanges (): void { | 28 | 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) { | |||
175 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy | 175 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy |
176 | if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled | 176 | if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled |
177 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo | 177 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo |
178 | if (body.autoPlayNextVideo !== undefined) user.autoPlayNextVideo = body.autoPlayNextVideo | ||
178 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 179 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled |
179 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 180 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages |
180 | if (body.theme !== undefined) user.theme = body.theme | 181 | 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) { | |||
65 | return isBooleanValid(value) | 65 | return isBooleanValid(value) |
66 | } | 66 | } |
67 | 67 | ||
68 | function isUserAutoPlayNextVideoValid (value: any) { | ||
69 | return isBooleanValid(value) | ||
70 | } | ||
71 | |||
68 | function isNoInstanceConfigWarningModal (value: any) { | 72 | function isNoInstanceConfigWarningModal (value: any) { |
69 | return isBooleanValid(value) | 73 | return isBooleanValid(value) |
70 | } | 74 | } |
@@ -106,6 +110,7 @@ export { | |||
106 | isUserNSFWPolicyValid, | 110 | isUserNSFWPolicyValid, |
107 | isUserWebTorrentEnabledValid, | 111 | isUserWebTorrentEnabledValid, |
108 | isUserAutoPlayVideoValid, | 112 | isUserAutoPlayVideoValid, |
113 | isUserAutoPlayNextVideoValid, | ||
109 | isUserDisplayNameValid, | 114 | isUserDisplayNameValid, |
110 | isUserDescriptionValid, | 115 | isUserDescriptionValid, |
111 | isNoInstanceConfigWarningModal, | 116 | 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 { | |||
25 | isNoInstanceConfigWarningModal, | 25 | isNoInstanceConfigWarningModal, |
26 | isUserAdminFlagsValid, | 26 | isUserAdminFlagsValid, |
27 | isUserAutoPlayVideoValid, | 27 | isUserAutoPlayVideoValid, |
28 | isUserAutoPlayNextVideoValid, | ||
28 | isUserBlockedReasonValid, | 29 | isUserBlockedReasonValid, |
29 | isUserBlockedValid, | 30 | isUserBlockedValid, |
30 | isUserEmailVerifiedValid, | 31 | isUserEmailVerifiedValid, |
@@ -160,6 +161,12 @@ export class UserModel extends Model<UserModel> { | |||
160 | @Column | 161 | @Column |
161 | autoPlayVideo: boolean | 162 | autoPlayVideo: boolean |
162 | 163 | ||
164 | @AllowNull(false) | ||
165 | @Default(false) | ||
166 | @Is('UserAutoPlayNextVideo', value => throwIfNotValid(value, isUserAutoPlayNextVideoValid, 'auto play next video boolean')) | ||
167 | @Column | ||
168 | autoPlayNextVideo: boolean | ||
169 | |||
163 | @AllowNull(true) | 170 | @AllowNull(true) |
164 | @Default(null) | 171 | @Default(null) |
165 | @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages')) | 172 | @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages')) |
@@ -597,6 +604,7 @@ export class UserModel extends Model<UserModel> { | |||
597 | webTorrentEnabled: this.webTorrentEnabled, | 604 | webTorrentEnabled: this.webTorrentEnabled, |
598 | videosHistoryEnabled: this.videosHistoryEnabled, | 605 | videosHistoryEnabled: this.videosHistoryEnabled, |
599 | autoPlayVideo: this.autoPlayVideo, | 606 | autoPlayVideo: this.autoPlayVideo, |
607 | autoPlayNextVideo: this.autoPlayNextVideo, | ||
600 | videoLanguages: this.videoLanguages, | 608 | videoLanguages: this.videoLanguages, |
601 | 609 | ||
602 | role: this.role, | 610 | 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 () { | |||
418 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | 418 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) |
419 | }) | 419 | }) |
420 | 420 | ||
421 | it('Should fail with an invalid autoPlayNextVideo attribute', async function () { | ||
422 | const fields = { | ||
423 | autoPlayNextVideo: -1 | ||
424 | } | ||
425 | |||
426 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
427 | }) | ||
428 | |||
421 | it('Should fail with an invalid videosHistoryEnabled attribute', async function () { | 429 | it('Should fail with an invalid videosHistoryEnabled attribute', async function () { |
422 | const fields = { | 430 | const fields = { |
423 | videosHistoryEnabled: -1 | 431 | 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 () { | |||
481 | expect(user.autoPlayVideo).to.be.false | 481 | expect(user.autoPlayVideo).to.be.false |
482 | }) | 482 | }) |
483 | 483 | ||
484 | it('Should be able to change the autoPlayNextVideo attribute', async function () { | ||
485 | await updateMyUser({ | ||
486 | url: server.url, | ||
487 | accessToken: accessTokenUser, | ||
488 | autoPlayNextVideo: true | ||
489 | }) | ||
490 | |||
491 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
492 | const user = res.body | ||
493 | |||
494 | expect(user.autoPlayNextVideo).to.be.true | ||
495 | }) | ||
496 | |||
484 | it('Should be able to change the email attribute', async function () { | 497 | it('Should be able to change the email attribute', async function () { |
485 | await updateMyUser({ | 498 | await updateMyUser({ |
486 | url: server.url, | 499 | 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 { | |||
7 | 7 | ||
8 | webTorrentEnabled?: boolean | 8 | webTorrentEnabled?: boolean |
9 | autoPlayVideo?: boolean | 9 | autoPlayVideo?: boolean |
10 | autoPlayNextVideo?: boolean | ||
10 | videosHistoryEnabled?: boolean | 11 | videosHistoryEnabled?: boolean |
11 | videoLanguages?: string[] | 12 | videoLanguages?: string[] |
12 | 13 | ||
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 { | |||
17 | adminFlags?: UserAdminFlag | 17 | adminFlags?: UserAdminFlag |
18 | 18 | ||
19 | autoPlayVideo: boolean | 19 | autoPlayVideo: boolean |
20 | autoPlayNextVideo: boolean | ||
20 | webTorrentEnabled: boolean | 21 | webTorrentEnabled: boolean |
21 | videosHistoryEnabled: boolean | 22 | videosHistoryEnabled: boolean |
22 | videoLanguages: string[] | 23 | videoLanguages: string[] |