mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-08-17 17:30:32 +03:00
Stop loop recommendation for anonymous users
This commit is contained in:
parent
671c6c1f96
commit
ada73259ff
@ -9,7 +9,7 @@ import { VideoCommentService } from '@app/shared/shared-video-comment/video-comm
|
||||
import { LiveVideoService } from '@app/shared/shared-video-live/live-video.service'
|
||||
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
|
||||
import { OverviewService } from '../video-list'
|
||||
import { RecentVideosRecommendationService, RecommendedVideosStore } from './shared'
|
||||
import { VideoRecommendationService } from './shared'
|
||||
import { VideoWatchComponent } from './video-watch.component'
|
||||
import { BulkService } from '@app/shared/shared-moderation/bulk.service'
|
||||
|
||||
@ -24,8 +24,7 @@ export default [
|
||||
VideoBlockService,
|
||||
LiveVideoService,
|
||||
VideoCommentService,
|
||||
RecentVideosRecommendationService,
|
||||
RecommendedVideosStore,
|
||||
VideoRecommendationService,
|
||||
SearchService,
|
||||
AbuseService,
|
||||
UserAdminService,
|
||||
|
@ -1,4 +1,2 @@
|
||||
export * from './recent-videos-recommendation.service'
|
||||
export * from './recommendation-info.model'
|
||||
export * from './video-recommendation.service'
|
||||
export * from './recommended-videos.component'
|
||||
export * from './recommended-videos.store'
|
||||
|
@ -1,4 +0,0 @@
|
||||
export interface RecommendationInfo {
|
||||
uuid: string
|
||||
tags?: string[]
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { Observable } from 'rxjs'
|
||||
import { RecommendationInfo } from './recommendation-info.model'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
|
||||
export interface RecommendationService {
|
||||
getRecommendations (recommendation: RecommendationInfo): Observable<Video[]>
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
<div class="other-videos" [ngClass]="{ 'display-as-row': displayAsRow }">
|
||||
<ng-container *ngIf="hasVideos$ | async">
|
||||
@if (videos.length !== 0) {
|
||||
<div class="title-page-container">
|
||||
<h2 i18n class="title-page">Other videos</h2>
|
||||
|
||||
<div *ngIf="!playlist" class="title-page-autoplay"
|
||||
<div
|
||||
*ngIf="!playlist" class="title-page-autoplay"
|
||||
[ngbTooltip]="autoPlayNextVideoTooltip" placement="bottom-right auto"
|
||||
>
|
||||
<span i18n>AUTOPLAY</span>
|
||||
|
||||
<my-input-switch
|
||||
i18n-label label="Toggle autoplay next video"
|
||||
class="small" inputName="autoplay-next-video"
|
||||
@ -15,8 +17,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let video of (videos$ | async); let i = index; let length = count">
|
||||
<ng-container *ngFor="let video of videos; let i = index; let length = count">
|
||||
<span i18n *ngIf="!playlist && i === 0 && length !== 0 && autoPlayNextVideo" class="title-page-next-video-label">Next video to be played</span>
|
||||
|
||||
<my-video-miniature
|
||||
[displayOptions]="displayOptions" [video]="video" [user]="user" [displayAsRow]="displayAsRow"
|
||||
(videoBlocked)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()" (videoAccountMuted)="onVideoRemoved()"
|
||||
@ -26,5 +29,5 @@
|
||||
|
||||
<hr *ngIf="!playlist && i === 0 && length > 1 && autoPlayNextVideo" />
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Observable, startWith, Subscription, switchMap } from 'rxjs'
|
||||
import { AsyncPipe, NgClass, NgFor, NgIf } from '@angular/common'
|
||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'
|
||||
import { AuthService, Notifier, User, UserService } from '@app/core'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
import { RecommendationInfo } from './recommendation-info.model'
|
||||
import { RecommendedVideosStore } from './recommended-videos.store'
|
||||
import { MiniatureDisplayOptions, VideoMiniatureComponent } from '../../../../shared/shared-video-miniature/video-miniature.component'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { InputSwitchComponent } from '../../../../shared/shared-forms/input-switch.component'
|
||||
import { AuthService, Notifier, User, UserService } from '@app/core'
|
||||
import { VideoDetails } from '@app/shared/shared-main/video/video-details.model'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgClass, NgIf, NgFor, AsyncPipe } from '@angular/common'
|
||||
import { VideoPlaylist } from '@peertube/peertube-models'
|
||||
import { Subscription, startWith, switchMap } from 'rxjs'
|
||||
import { InputSwitchComponent } from '../../../../shared/shared-forms/input-switch.component'
|
||||
import { MiniatureDisplayOptions, VideoMiniatureComponent } from '../../../../shared/shared-video-miniature/video-miniature.component'
|
||||
import { VideoRecommendationService } from './video-recommendation.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-recommended-videos',
|
||||
@ -19,12 +19,14 @@ import { VideoPlaylist } from '@peertube/peertube-models'
|
||||
imports: [ NgClass, NgIf, NgbTooltip, InputSwitchComponent, FormsModule, NgFor, VideoMiniatureComponent, AsyncPipe ]
|
||||
})
|
||||
export class RecommendedVideosComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() inputRecommendation: RecommendationInfo
|
||||
@Input() currentVideo: VideoDetails
|
||||
@Input() playlist: VideoPlaylist
|
||||
@Input() displayAsRow: boolean
|
||||
|
||||
@Output() gotRecommendations = new EventEmitter<Video[]>()
|
||||
|
||||
videos: Video[] = []
|
||||
|
||||
autoPlayNextVideo: boolean
|
||||
autoPlayNextVideoTooltip: string
|
||||
|
||||
@ -39,19 +41,12 @@ export class RecommendedVideosComponent implements OnInit, OnChanges, OnDestroy
|
||||
|
||||
private userSub: Subscription
|
||||
|
||||
readonly hasVideos$: Observable<boolean>
|
||||
readonly videos$: Observable<Video[]>
|
||||
|
||||
constructor (
|
||||
private userService: UserService,
|
||||
private authService: AuthService,
|
||||
private notifier: Notifier,
|
||||
private store: RecommendedVideosStore
|
||||
private videoRecommendation: VideoRecommendationService
|
||||
) {
|
||||
this.videos$ = this.store.recommendations$
|
||||
this.hasVideos$ = this.store.hasRecommendations$
|
||||
this.videos$.subscribe(videos => this.gotRecommendations.emit(videos))
|
||||
|
||||
this.autoPlayNextVideoTooltip = $localize`When active, the next video is automatically played after the current one.`
|
||||
}
|
||||
|
||||
@ -68,8 +63,8 @@ export class RecommendedVideosComponent implements OnInit, OnChanges, OnDestroy
|
||||
}
|
||||
|
||||
ngOnChanges () {
|
||||
if (this.inputRecommendation) {
|
||||
this.store.requestNewRecommendations(this.inputRecommendation)
|
||||
if (this.currentVideo) {
|
||||
this.loadRecommendations()
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +73,7 @@ export class RecommendedVideosComponent implements OnInit, OnChanges, OnDestroy
|
||||
}
|
||||
|
||||
onVideoRemoved () {
|
||||
this.store.requestNewRecommendations(this.inputRecommendation)
|
||||
this.loadRecommendations()
|
||||
}
|
||||
|
||||
switchAutoPlayNextVideo () {
|
||||
@ -97,4 +92,17 @@ export class RecommendedVideosComponent implements OnInit, OnChanges, OnDestroy
|
||||
this.userService.updateMyAnonymousProfile(details)
|
||||
}
|
||||
}
|
||||
|
||||
private loadRecommendations () {
|
||||
this.videoRecommendation.getRecommendations(this.currentVideo, this.videoRecommendation.getRecommentationHistory())
|
||||
.subscribe({
|
||||
next: videos => {
|
||||
this.videos = videos
|
||||
|
||||
this.gotRecommendations.emit(this.videos)
|
||||
},
|
||||
|
||||
error: err => this.notifier.error(err.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
import { Observable, ReplaySubject } from 'rxjs'
|
||||
import { map, shareReplay, switchMap, take } from 'rxjs/operators'
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
import { RecentVideosRecommendationService } from './recent-videos-recommendation.service'
|
||||
import { RecommendationInfo } from './recommendation-info.model'
|
||||
import { RecommendationService } from './recommendations.service'
|
||||
|
||||
/**
|
||||
* This store is intended to provide data for the RecommendedVideosComponent.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RecommendedVideosStore {
|
||||
public readonly recommendations$: Observable<Video[]>
|
||||
public readonly hasRecommendations$: Observable<boolean>
|
||||
private readonly requestsForLoad$$ = new ReplaySubject<RecommendationInfo>(1)
|
||||
|
||||
constructor (
|
||||
@Inject(RecentVideosRecommendationService) private recommendations: RecommendationService
|
||||
) {
|
||||
this.recommendations$ = this.requestsForLoad$$.pipe(
|
||||
switchMap(requestedRecommendation => {
|
||||
return this.recommendations.getRecommendations(requestedRecommendation)
|
||||
.pipe(take(1))
|
||||
}),
|
||||
shareReplay()
|
||||
)
|
||||
|
||||
this.hasRecommendations$ = this.recommendations$.pipe(
|
||||
map(otherVideos => otherVideos.length > 0)
|
||||
)
|
||||
}
|
||||
|
||||
requestNewRecommendations (recommend: RecommendationInfo) {
|
||||
this.requestsForLoad$$.next(recommend)
|
||||
}
|
||||
}
|
@ -1,24 +1,20 @@
|
||||
import { Observable, of } from 'rxjs'
|
||||
import { map, switchMap } from 'rxjs/operators'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ServerService, UserService } from '@app/core'
|
||||
import { HTMLServerConfig } from '@peertube/peertube-models'
|
||||
import { RecommendationInfo } from './recommendation-info.model'
|
||||
import { RecommendationService } from './recommendations.service'
|
||||
import { VideoDetails } from '@app/shared/shared-main/video/video-details.model'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
import { VideoService } from '@app/shared/shared-main/video/video.service'
|
||||
import { SearchService } from '@app/shared/shared-search/search.service'
|
||||
import { AdvancedSearch } from '@app/shared/shared-search/advanced-search.model'
|
||||
import { SearchService } from '@app/shared/shared-search/search.service'
|
||||
import { HTMLServerConfig } from '@peertube/peertube-models'
|
||||
import { Observable, of } from 'rxjs'
|
||||
import { map, switchMap } from 'rxjs/operators'
|
||||
|
||||
/**
|
||||
* Provides "recommendations" by providing the most recently uploaded videos.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RecentVideosRecommendationService implements RecommendationService {
|
||||
readonly pageSize = 5
|
||||
|
||||
export class VideoRecommendationService {
|
||||
private config: HTMLServerConfig
|
||||
|
||||
private readonly videoIdsHistory = new Set<number>()
|
||||
|
||||
constructor (
|
||||
private videos: VideoService,
|
||||
private searchService: SearchService,
|
||||
@ -28,19 +24,37 @@ export class RecentVideosRecommendationService implements RecommendationService
|
||||
this.config = this.serverService.getHTMLConfig()
|
||||
}
|
||||
|
||||
getRecommendations (recommendation: RecommendationInfo): Observable<Video[]> {
|
||||
getRecommentationHistory () {
|
||||
return this.videoIdsHistory
|
||||
}
|
||||
|
||||
return this.fetchPage(1, recommendation)
|
||||
getRecommendations (currentVideo: VideoDetails, exceptions = new Set<number>()): Observable<Video[]> {
|
||||
this.videoIdsHistory.add(currentVideo.id)
|
||||
|
||||
// We want 5 results max
|
||||
// +1 to exclude the currentVideo if needed
|
||||
// +exceptions.size to exclude the videos we don't want to include
|
||||
// Cap to 30 results maximum
|
||||
const totalVideos = 5
|
||||
const internalTotalVideos = Math.min(totalVideos + 1 + exceptions.size, 30)
|
||||
|
||||
return this.fetchPage(currentVideo, internalTotalVideos)
|
||||
.pipe(
|
||||
map(videos => {
|
||||
const otherVideos = videos.filter(v => v.uuid !== recommendation.uuid)
|
||||
return otherVideos.slice(0, this.pageSize)
|
||||
let otherVideos = videos.filter(v => v.uuid !== currentVideo.uuid && !exceptions.has(v.id))
|
||||
|
||||
// Stop using exclude list if we excluded all videos
|
||||
if (otherVideos.length === 0 && videos.length !== 0) {
|
||||
otherVideos = videos.filter(v => v.uuid !== currentVideo.uuid)
|
||||
}
|
||||
|
||||
return otherVideos.slice(0, totalVideos)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fetchPage (page: number, recommendation: RecommendationInfo): Observable<Video[]> {
|
||||
const pagination = { currentPage: page, itemsPerPage: this.pageSize + 1 }
|
||||
private fetchPage (currentVideo: VideoDetails, totalItems: number): Observable<Video[]> {
|
||||
const pagination = { currentPage: 1, itemsPerPage: totalItems }
|
||||
|
||||
return this.userService.getAnonymousOrLoggedUser()
|
||||
.pipe(
|
||||
@ -66,7 +80,7 @@ export class RecentVideosRecommendationService implements RecommendationService
|
||||
componentPagination: pagination,
|
||||
skipCount: true,
|
||||
advancedSearch: new AdvancedSearch({
|
||||
tagsOneOf: recommendation.tags.join(','),
|
||||
tagsOneOf: currentVideo.tags.join(','),
|
||||
sort: '-publishedAt',
|
||||
searchTarget: 'local',
|
||||
nsfw,
|
@ -102,7 +102,7 @@
|
||||
|
||||
<my-recommended-videos
|
||||
[displayAsRow]="displayOtherVideosAsRow()"
|
||||
[inputRecommendation]="{ uuid: video.uuid, tags: video.tags }"
|
||||
[currentVideo]="video"
|
||||
[playlist]="playlist"
|
||||
(gotRecommendations)="onRecommendations($event)"
|
||||
></my-recommended-videos>
|
||||
|
@ -1,25 +1,34 @@
|
||||
import { forkJoin, map, Observable, of, Subscription, switchMap } from 'rxjs'
|
||||
import { PlatformLocation, NgClass, NgIf, NgTemplateOutlet } from '@angular/common'
|
||||
import { NgClass, NgIf, NgTemplateOutlet, PlatformLocation } from '@angular/common'
|
||||
import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router'
|
||||
import {
|
||||
AuthService,
|
||||
AuthUser,
|
||||
ConfirmService,
|
||||
Hotkey,
|
||||
HotkeysService,
|
||||
MetaService,
|
||||
Notifier,
|
||||
PeerTubeSocket,
|
||||
PluginService,
|
||||
RestExtractor,
|
||||
ScreenService,
|
||||
ServerService,
|
||||
Hotkey,
|
||||
HotkeysService,
|
||||
User,
|
||||
UserService,
|
||||
MetaService
|
||||
UserService
|
||||
} from '@app/core'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
import { isXPercentInViewport, scrollToTop, toBoolean } from '@app/helpers'
|
||||
import { VideoCaptionService } from '@app/shared/shared-main/video-caption/video-caption.service'
|
||||
import { VideoChapterService } from '@app/shared/shared-main/video/video-chapter.service'
|
||||
import { VideoDetails } from '@app/shared/shared-main/video/video-details.model'
|
||||
import { VideoFileTokenService } from '@app/shared/shared-main/video/video-file-token.service'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
import { VideoService } from '@app/shared/shared-main/video/video.service'
|
||||
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription/subscribe-button.component'
|
||||
import { LiveVideoService } from '@app/shared/shared-video-live/live-video.service'
|
||||
import { VideoPlaylist } from '@app/shared/shared-video-playlist/video-playlist.model'
|
||||
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
|
||||
import { timeToInt } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
HTMLServerConfig,
|
||||
@ -36,6 +45,7 @@ import {
|
||||
} from '@peertube/peertube-models'
|
||||
import { logger } from '@root-helpers/logger'
|
||||
import { isP2PEnabled, videoRequiresFileToken, videoRequiresUserAuth } from '@root-helpers/video'
|
||||
import { forkJoin, map, Observable, of, Subscription, switchMap } from 'rxjs'
|
||||
import {
|
||||
HLSOptions,
|
||||
PeerTubePlayer,
|
||||
@ -46,29 +56,19 @@ import {
|
||||
} from '../../../assets/player'
|
||||
import { cleanupVideoWatch, getStoredTheater, getStoredVideoWatchHistory } from '../../../assets/player/peertube-player-local-storage'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { VideoWatchPlaylistComponent } from './shared'
|
||||
import { PlayerStylesComponent } from './player-styles.component'
|
||||
import { PrivacyConcernsComponent } from './shared/information/privacy-concerns.component'
|
||||
import { RecommendedVideosComponent } from './shared/recommendations/recommended-videos.component'
|
||||
import { VideoCommentsComponent } from './shared/comment/video-comments.component'
|
||||
import { VideoAttributesComponent } from './shared/metadata/video-attributes.component'
|
||||
import { VideoDescriptionComponent } from './shared/metadata/video-description.component'
|
||||
import { VideoAvatarChannelComponent } from './shared/metadata/video-avatar-channel.component'
|
||||
import { ActionButtonsComponent } from './shared/action-buttons/action-buttons.component'
|
||||
import { VideoViewsCounterComponent } from '../../shared/shared-video/video-views-counter.component'
|
||||
import { DateToggleComponent } from '../../shared/shared-main/date/date-toggle.component'
|
||||
import { VideoAlertComponent } from './shared/information/video-alert.component'
|
||||
import { PluginPlaceholderComponent } from '../../shared/shared-main/plugins/plugin-placeholder.component'
|
||||
import { VideoDetails } from '@app/shared/shared-main/video/video-details.model'
|
||||
import { VideoCaptionService } from '@app/shared/shared-main/video-caption/video-caption.service'
|
||||
import { VideoChapterService } from '@app/shared/shared-main/video/video-chapter.service'
|
||||
import { VideoFileTokenService } from '@app/shared/shared-main/video/video-file-token.service'
|
||||
import { VideoService } from '@app/shared/shared-main/video/video.service'
|
||||
import { Video } from '@app/shared/shared-main/video/video.model'
|
||||
import { VideoPlaylist } from '@app/shared/shared-video-playlist/video-playlist.model'
|
||||
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription/subscribe-button.component'
|
||||
import { LiveVideoService } from '@app/shared/shared-video-live/live-video.service'
|
||||
import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service'
|
||||
import { VideoViewsCounterComponent } from '../../shared/shared-video/video-views-counter.component'
|
||||
import { PlayerStylesComponent } from './player-styles.component'
|
||||
import { VideoWatchPlaylistComponent } from './shared'
|
||||
import { ActionButtonsComponent } from './shared/action-buttons/action-buttons.component'
|
||||
import { VideoCommentsComponent } from './shared/comment/video-comments.component'
|
||||
import { PrivacyConcernsComponent } from './shared/information/privacy-concerns.component'
|
||||
import { VideoAlertComponent } from './shared/information/video-alert.component'
|
||||
import { VideoAttributesComponent } from './shared/metadata/video-attributes.component'
|
||||
import { VideoAvatarChannelComponent } from './shared/metadata/video-avatar-channel.component'
|
||||
import { VideoDescriptionComponent } from './shared/metadata/video-description.component'
|
||||
import { RecommendedVideosComponent } from './shared/recommendations/recommended-videos.component'
|
||||
|
||||
type URLOptions = {
|
||||
playerMode: PlayerMode
|
||||
|
Loading…
Reference in New Issue
Block a user