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
This commit is contained in:
LoveIsGrief 2019-09-24 08:48:01 +02:00 committed by Chocobozzz
parent 32d7f2b754
commit 6aa5414813
14 changed files with 74 additions and 2 deletions

2
.gitignore vendored
View File

@ -37,6 +37,8 @@
/scripts/i18n/generate-iso639-target.ts
# Other
/dump.rdb
/.theia/
/profiling/
/*.zip
/*.tar.xz

View File

@ -47,6 +47,11 @@
inputName="autoPlayVideo" formControlName="autoPlayVideo"
i18n-labelText labelText="Automatically plays video"
></my-peertube-checkbox>
<my-peertube-checkbox
inputName="autoPlayNextVideo" formControlName="autoPlayNextVideo"
i18n-labelText labelText="Automatically starts playing next video"
></my-peertube-checkbox>
</div>
<input type="submit" i18n-value value="Save" [disabled]="!form.valid">

View File

@ -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
}

View File

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

View File

@ -199,7 +199,11 @@
<my-video-comments [video]="video" [user]="user"></my-video-comments>
</div>
<my-recommended-videos [inputRecommendation]="{ uuid: video.uuid, tags: video.tags }" [user]="user"></my-recommended-videos>
<my-recommended-videos
[inputRecommendation]="{ uuid: video.uuid, tags: video.tags }"
[user]="user"
(gotRecommendations)="onRecommendations($event)"
></my-recommended-videos>
</div>
<div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false">

View File

@ -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<any> } = {
like: this.videoService.setVideoLike,

View File

@ -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<Video[]>()
readonly hasVideos$: Observable<boolean>
readonly videos$: Observable<Video[]>
@ -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 {

View File

@ -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

View File

@ -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,

View File

@ -25,6 +25,7 @@ import {
isNoInstanceConfigWarningModal,
isUserAdminFlagsValid,
isUserAutoPlayVideoValid,
isUserAutoPlayNextVideoValid,
isUserBlockedReasonValid,
isUserBlockedValid,
isUserEmailVerifiedValid,
@ -160,6 +161,12 @@ export class UserModel extends Model<UserModel> {
@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<UserModel> {
webTorrentEnabled: this.webTorrentEnabled,
videosHistoryEnabled: this.videosHistoryEnabled,
autoPlayVideo: this.autoPlayVideo,
autoPlayNextVideo: this.autoPlayNextVideo,
videoLanguages: this.videoLanguages,
role: this.role,

View File

@ -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

View File

@ -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,

View File

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

View File

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