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
pull/2158/head
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 /scripts/i18n/generate-iso639-target.ts
# Other # Other
/dump.rdb
/.theia/
/profiling/ /profiling/
/*.zip /*.zip
/*.tar.xz /*.tar.xz

View File

@ -47,6 +47,11 @@
inputName="autoPlayVideo" formControlName="autoPlayVideo" inputName="autoPlayVideo" formControlName="autoPlayVideo"
i18n-labelText labelText="Automatically plays video" i18n-labelText labelText="Automatically plays video"
></my-peertube-checkbox> ></my-peertube-checkbox>
<my-peertube-checkbox
inputName="autoPlayNextVideo" formControlName="autoPlayNextVideo"
i18n-labelText labelText="Automatically starts playing next video"
></my-peertube-checkbox>
</div> </div>
<input type="submit" i18n-value value="Save" [disabled]="!form.valid"> <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, nsfwPolicy: null,
webTorrentEnabled: null, webTorrentEnabled: null,
autoPlayVideo: null, autoPlayVideo: null,
autoPlayNextVideo: null,
videoLanguages: null videoLanguages: null
}) })
@ -57,6 +58,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
nsfwPolicy: this.user.nsfwPolicy, nsfwPolicy: this.user.nsfwPolicy,
webTorrentEnabled: this.user.webTorrentEnabled, webTorrentEnabled: this.user.webTorrentEnabled,
autoPlayVideo: this.user.autoPlayVideo === true, autoPlayVideo: this.user.autoPlayVideo === true,
autoPlayNextVideo: this.user.autoPlayNextVideo,
videoLanguages videoLanguages
}) })
}) })
@ -66,6 +68,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] const nsfwPolicy = this.form.value[ 'nsfwPolicy' ]
const webTorrentEnabled = this.form.value['webTorrentEnabled'] const webTorrentEnabled = this.form.value['webTorrentEnabled']
const autoPlayVideo = this.form.value['autoPlayVideo'] const autoPlayVideo = this.form.value['autoPlayVideo']
const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
let videoLanguages: string[] = this.form.value['videoLanguages'] let videoLanguages: string[] = this.form.value['videoLanguages']
if (Array.isArray(videoLanguages)) { if (Array.isArray(videoLanguages)) {
@ -84,6 +87,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
nsfwPolicy, nsfwPolicy,
webTorrentEnabled, webTorrentEnabled,
autoPlayVideo, autoPlayVideo,
autoPlayNextVideo,
videoLanguages videoLanguages
} }

View File

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

View File

@ -199,7 +199,11 @@
<my-video-comments [video]="video" [user]="user"></my-video-comments> <my-video-comments [video]="video" [user]="user"></my-video-comments>
</div> </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>
<div class="row privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> <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 { PluginService } from '@app/core/plugins/plugin.service'
import { HooksService } from '@app/core/plugins/hooks.service' import { HooksService } from '@app/core/plugins/hooks.service'
import { PlatformLocation } from '@angular/common' import { PlatformLocation } from '@angular/common'
import { randomInt } from '@shared/core-utils/miscs/miscs'
@Component({ @Component({
selector: 'my-video-watch', selector: 'my-video-watch',
@ -69,6 +70,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
remoteServerDown = false remoteServerDown = false
hotkeys: Hotkey[] hotkeys: Hotkey[]
private nextVideoUuid = ''
private currentTime: number private currentTime: number
private paramsSub: Subscription private paramsSub: Subscription
private queryParamsSub: Subscription private queryParamsSub: Subscription
@ -217,6 +219,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
return this.video.tags 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 () { onVideoRemoved () {
this.redirectService.redirectToHomepage() this.redirectService.redirectToHomepage()
} }
@ -477,6 +486,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.player.one('ended', () => { this.player.one('ended', () => {
if (this.playlist) { if (this.playlist) {
this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) 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') 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) { private setRating (nextRating: UserVideoRateType) {
const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = { const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = {
like: this.videoService.setVideoLike, 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 { Observable } from 'rxjs'
import { Video } from '@app/shared/video/video.model' import { Video } from '@app/shared/video/video.model'
import { RecommendationInfo } from '@app/shared/video/recommendation-info.model' import { RecommendationInfo } from '@app/shared/video/recommendation-info.model'
@ -12,6 +12,7 @@ import { User } from '@app/shared'
export class RecommendedVideosComponent implements OnChanges { export class RecommendedVideosComponent implements OnChanges {
@Input() inputRecommendation: RecommendationInfo @Input() inputRecommendation: RecommendationInfo
@Input() user: User @Input() user: User
@Output() gotRecommendations = new EventEmitter<Video[]>()
readonly hasVideos$: Observable<boolean> readonly hasVideos$: Observable<boolean>
readonly videos$: Observable<Video[]> readonly videos$: Observable<Video[]>
@ -21,6 +22,7 @@ export class RecommendedVideosComponent implements OnChanges {
) { ) {
this.videos$ = this.store.recommendations$ this.videos$ = this.store.recommendations$
this.hasVideos$ = this.store.hasRecommendations$ this.hasVideos$ = this.store.hasRecommendations$
this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
} }
public ngOnChanges (): void { 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.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo 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.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages
if (body.theme !== undefined) user.theme = body.theme if (body.theme !== undefined) user.theme = body.theme

View File

@ -65,6 +65,10 @@ function isUserBlockedValid (value: any) {
return isBooleanValid(value) return isBooleanValid(value)
} }
function isUserAutoPlayNextVideoValid (value: any) {
return isBooleanValid(value)
}
function isNoInstanceConfigWarningModal (value: any) { function isNoInstanceConfigWarningModal (value: any) {
return isBooleanValid(value) return isBooleanValid(value)
} }
@ -106,6 +110,7 @@ export {
isUserNSFWPolicyValid, isUserNSFWPolicyValid,
isUserWebTorrentEnabledValid, isUserWebTorrentEnabledValid,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserAutoPlayNextVideoValid,
isUserDisplayNameValid, isUserDisplayNameValid,
isUserDescriptionValid, isUserDescriptionValid,
isNoInstanceConfigWarningModal, isNoInstanceConfigWarningModal,

View File

@ -25,6 +25,7 @@ import {
isNoInstanceConfigWarningModal, isNoInstanceConfigWarningModal,
isUserAdminFlagsValid, isUserAdminFlagsValid,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserAutoPlayNextVideoValid,
isUserBlockedReasonValid, isUserBlockedReasonValid,
isUserBlockedValid, isUserBlockedValid,
isUserEmailVerifiedValid, isUserEmailVerifiedValid,
@ -160,6 +161,12 @@ export class UserModel extends Model<UserModel> {
@Column @Column
autoPlayVideo: boolean autoPlayVideo: boolean
@AllowNull(false)
@Default(false)
@Is('UserAutoPlayNextVideo', value => throwIfNotValid(value, isUserAutoPlayNextVideoValid, 'auto play next video boolean'))
@Column
autoPlayNextVideo: boolean
@AllowNull(true) @AllowNull(true)
@Default(null) @Default(null)
@Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages')) @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages'))
@ -597,6 +604,7 @@ export class UserModel extends Model<UserModel> {
webTorrentEnabled: this.webTorrentEnabled, webTorrentEnabled: this.webTorrentEnabled,
videosHistoryEnabled: this.videosHistoryEnabled, videosHistoryEnabled: this.videosHistoryEnabled,
autoPlayVideo: this.autoPlayVideo, autoPlayVideo: this.autoPlayVideo,
autoPlayNextVideo: this.autoPlayNextVideo,
videoLanguages: this.videoLanguages, videoLanguages: this.videoLanguages,
role: this.role, 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 }) 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 () { it('Should fail with an invalid videosHistoryEnabled attribute', async function () {
const fields = { const fields = {
videosHistoryEnabled: -1 videosHistoryEnabled: -1

View File

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

View File

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

View File

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