Browse Source

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
LoveIsGrief 1 month ago
parent
commit
6aa5414813

+ 2
- 0
.gitignore View File

@@ -37,6 +37,8 @@
37 37
 /scripts/i18n/generate-iso639-target.ts
38 38
 
39 39
 # Other
40
+/dump.rdb
41
+/.theia/
40 42
 /profiling/
41 43
 /*.zip
42 44
 /*.tar.xz

+ 5
- 0
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html View File

@@ -47,6 +47,11 @@
47 47
       inputName="autoPlayVideo" formControlName="autoPlayVideo"
48 48
       i18n-labelText labelText="Automatically plays video"
49 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 55
   </div>
51 56
 
52 57
   <input type="submit" i18n-value value="Save" [disabled]="!form.valid">

+ 4
- 0
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts View File

@@ -36,6 +36,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
36 36
       nsfwPolicy: null,
37 37
       webTorrentEnabled: null,
38 38
       autoPlayVideo: null,
39
+      autoPlayNextVideo: null,
39 40
       videoLanguages: null
40 41
     })
41 42
 
@@ -57,6 +58,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
57 58
         nsfwPolicy: this.user.nsfwPolicy,
58 59
         webTorrentEnabled: this.user.webTorrentEnabled,
59 60
         autoPlayVideo: this.user.autoPlayVideo === true,
61
+        autoPlayNextVideo: this.user.autoPlayNextVideo,
60 62
         videoLanguages
61 63
       })
62 64
     })
@@ -66,6 +68,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
66 68
     const nsfwPolicy = this.form.value[ 'nsfwPolicy' ]
67 69
     const webTorrentEnabled = this.form.value['webTorrentEnabled']
68 70
     const autoPlayVideo = this.form.value['autoPlayVideo']
71
+    const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
69 72
 
70 73
     let videoLanguages: string[] = this.form.value['videoLanguages']
71 74
     if (Array.isArray(videoLanguages)) {
@@ -84,6 +87,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
84 87
       nsfwPolicy,
85 88
       webTorrentEnabled,
86 89
       autoPlayVideo,
90
+      autoPlayNextVideo,
87 91
       videoLanguages
88 92
     }
89 93
 

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

@@ -16,6 +16,7 @@ export class User implements UserServerModel {
16 16
   adminFlags?: UserAdminFlag
17 17
 
18 18
   autoPlayVideo: boolean
19
+  autoPlayNextVideo: boolean
19 20
   webTorrentEnabled: boolean
20 21
   videosHistoryEnabled: boolean
21 22
   videoLanguages: string[]

+ 5
- 1
client/src/app/videos/+video-watch/video-watch.component.html View File

@@ -199,7 +199,11 @@
199 199
       <my-video-comments [video]="video" [user]="user"></my-video-comments>
200 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 207
   </div>
204 208
 
205 209
   <div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false">

+ 17
- 0
client/src/app/videos/+video-watch/video-watch.component.ts View File

@@ -35,6 +35,7 @@ import { getStoredTheater } from '../../../assets/player/peertube-player-local-s
35 35
 import { PluginService } from '@app/core/plugins/plugin.service'
36 36
 import { HooksService } from '@app/core/plugins/hooks.service'
37 37
 import { PlatformLocation } from '@angular/common'
38
+import { randomInt } from '@shared/core-utils/miscs/miscs'
38 39
 
39 40
 @Component({
40 41
   selector: 'my-video-watch',
@@ -69,6 +70,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
69 70
   remoteServerDown = false
70 71
   hotkeys: Hotkey[]
71 72
 
73
+  private nextVideoUuid = ''
72 74
   private currentTime: number
73 75
   private paramsSub: Subscription
74 76
   private queryParamsSub: Subscription
@@ -217,6 +219,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
217 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 229
   onVideoRemoved () {
221 230
     this.redirectService.redirectToHomepage()
222 231
   }
@@ -477,6 +486,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
477 486
       this.player.one('ended', () => {
478 487
         if (this.playlist) {
479 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 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 520
   private setRating (nextRating: UserVideoRateType) {
504 521
     const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = {
505 522
       like: this.videoService.setVideoLike,

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

@@ -1,4 +1,4 @@
1
-import { Component, Input, OnChanges } from '@angular/core'
1
+import { Component, Input, Output, OnChanges, EventEmitter } from '@angular/core'
2 2
 import { Observable } from 'rxjs'
3 3
 import { Video } from '@app/shared/video/video.model'
4 4
 import { RecommendationInfo } from '@app/shared/video/recommendation-info.model'
@@ -12,6 +12,7 @@ import { User } from '@app/shared'
12 12
 export class RecommendedVideosComponent implements OnChanges {
13 13
   @Input() inputRecommendation: RecommendationInfo
14 14
   @Input() user: User
15
+  @Output() gotRecommendations = new EventEmitter<Video[]>()
15 16
 
16 17
   readonly hasVideos$: Observable<boolean>
17 18
   readonly videos$: Observable<Video[]>
@@ -21,6 +22,7 @@ export class RecommendedVideosComponent implements OnChanges {
21 22
   ) {
22 23
     this.videos$ = this.store.recommendations$
23 24
     this.hasVideos$ = this.store.hasRecommendations$
25
+    this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
24 26
   }
25 27
 
26 28
   public ngOnChanges (): void {

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

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

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

@@ -65,6 +65,10 @@ function isUserBlockedValid (value: any) {
65 65
   return isBooleanValid(value)
66 66
 }
67 67
 
68
+function isUserAutoPlayNextVideoValid (value: any) {
69
+  return isBooleanValid(value)
70
+}
71
+
68 72
 function isNoInstanceConfigWarningModal (value: any) {
69 73
   return isBooleanValid(value)
70 74
 }
@@ -106,6 +110,7 @@ export {
106 110
   isUserNSFWPolicyValid,
107 111
   isUserWebTorrentEnabledValid,
108 112
   isUserAutoPlayVideoValid,
113
+  isUserAutoPlayNextVideoValid,
109 114
   isUserDisplayNameValid,
110 115
   isUserDescriptionValid,
111 116
   isNoInstanceConfigWarningModal,

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

@@ -25,6 +25,7 @@ import {
25 25
   isNoInstanceConfigWarningModal,
26 26
   isUserAdminFlagsValid,
27 27
   isUserAutoPlayVideoValid,
28
+  isUserAutoPlayNextVideoValid,
28 29
   isUserBlockedReasonValid,
29 30
   isUserBlockedValid,
30 31
   isUserEmailVerifiedValid,
@@ -160,6 +161,12 @@ export class UserModel extends Model<UserModel> {
160 161
   @Column
161 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 170
   @AllowNull(true)
164 171
   @Default(null)
165 172
   @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages'))
@@ -597,6 +604,7 @@ export class UserModel extends Model<UserModel> {
597 604
       webTorrentEnabled: this.webTorrentEnabled,
598 605
       videosHistoryEnabled: this.videosHistoryEnabled,
599 606
       autoPlayVideo: this.autoPlayVideo,
607
+      autoPlayNextVideo: this.autoPlayNextVideo,
600 608
       videoLanguages: this.videoLanguages,
601 609
 
602 610
       role: this.role,

+ 8
- 0
server/tests/api/check-params/users.ts View File

@@ -418,6 +418,14 @@ describe('Test users API validators', function () {
418 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 429
     it('Should fail with an invalid videosHistoryEnabled attribute', async function () {
422 430
       const fields = {
423 431
         videosHistoryEnabled: -1

+ 13
- 0
server/tests/api/users/users.ts View File

@@ -481,6 +481,19 @@ describe('Test users', function () {
481 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 497
     it('Should be able to change the email attribute', async function () {
485 498
       await updateMyUser({
486 499
         url: server.url,

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

@@ -7,6 +7,7 @@ export interface UserUpdateMe {
7 7
 
8 8
   webTorrentEnabled?: boolean
9 9
   autoPlayVideo?: boolean
10
+  autoPlayNextVideo?: boolean
10 11
   videosHistoryEnabled?: boolean
11 12
   videoLanguages?: string[]
12 13
 

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

@@ -17,6 +17,7 @@ export interface User {
17 17
   adminFlags?: UserAdminFlag
18 18
 
19 19
   autoPlayVideo: boolean
20
+  autoPlayNextVideo: boolean
20 21
   webTorrentEnabled: boolean
21 22
   videosHistoryEnabled: boolean
22 23
   videoLanguages: string[]

Loading…
Cancel
Save