Share playlists state
authorChocobozzz <me@florianbigard.com>
Mon, 6 Jan 2020 12:06:13 +0000 (13:06 +0100)
committerChocobozzz <me@florianbigard.com>
Mon, 6 Jan 2020 12:34:08 +0000 (13:34 +0100)
client/package.json
client/src/app/shared/video-playlist/video-add-to-playlist.component.ts
client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts
client/src/app/shared/video-playlist/video-playlist.service.ts
client/src/app/shared/video/video-miniature.component.ts
client/src/app/shared/video/video-thumbnail.component.html
client/yarn.lock
server/controllers/api/users/my-video-playlists.ts
shared/models/users/user.model.ts
shared/models/videos/playlist/video-exist-in-playlist.model.ts

index 53aedfebd17d8e428faca325b96fb08d7716a66e..6a1fd145cb3066ec2fe235ae235e0814c992dc6e 100644 (file)
@@ -55,6 +55,7 @@
     "@ngx-translate/i18n-polyfill": "^1.0.0",
     "@streamroot/videojs-hlsjs-plugin": "^1.0.10",
     "@types/core-js": "^2.5.2",
+    "@types/debug": "^4.1.5",
     "@types/hls.js": "^0.12.4",
     "@types/jasmine": "^3.3.15",
     "@types/jasminewd2": "^2.0.3",
@@ -76,6 +77,7 @@
     "codelyzer": "^5.0.1",
     "core-js": "^3.1.4",
     "css-loader": "^3.1.0",
+    "debug": "^4.1.1",
     "dexie": "^2.0.4",
     "extract-text-webpack-plugin": "4.0.0-beta.0",
     "file-loader": "^4.1.0",
index 25ba8cbcaa1d4ab0dfae993aff0e2f0e4975b59b..e60a8381d5e98bda0169d72a377d4f2ce703990b 100644 (file)
@@ -1,12 +1,18 @@
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
 import { AuthService, Notifier } from '@app/core'
-import { forkJoin, Subject } from 'rxjs'
-import { debounceTime } from 'rxjs/operators'
+import { Subject, Subscription } from 'rxjs'
+import { debounceTime, filter } from 'rxjs/operators'
 import { Video, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models'
 import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/forms'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { secondsToTime } from '../../../assets/player/utils'
+import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
+import * as debug from 'debug'
+import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
+import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
+
+const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
 
 type PlaylistSummary = {
   id: number
@@ -24,7 +30,7 @@ type PlaylistSummary = {
   templateUrl: './video-add-to-playlist.component.html',
   changeDetection: ChangeDetectionStrategy.OnPush
 })
-export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, OnChanges {
+export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, OnChanges, OnDestroy, DisableForReuseHook {
   @Input() video: Video
   @Input() currentVideoTimestamp: number
   @Input() lazyLoad = false
@@ -41,6 +47,11 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
   }
   displayOptions = false
 
+  private disabled = false
+
+  private listenToPlaylistChangeSub: Subscription
+  private playlistsData: VideoPlaylist[] = []
+
   constructor (
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
@@ -62,12 +73,18 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
       displayName: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME
     })
 
+    this.videoPlaylistService.listenToMyAccountPlaylistsChange()
+        .subscribe(result => {
+          this.playlistsData = result.data
+
+          this.videoPlaylistService.runPlaylistCheck(this.video.id)
+        })
+
     this.videoPlaylistSearchChanged
-      .pipe(
-        debounceTime(500))
-      .subscribe(() => {
-        this.load()
-      })
+        .pipe(debounceTime(500))
+        .subscribe(() => this.load())
+
+    if (this.lazyLoad === false) this.load()
   }
 
   ngOnChanges (simpleChanges: SimpleChanges) {
@@ -76,45 +93,41 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
     }
   }
 
-  init () {
-    this.resetOptions(true)
+  ngOnDestroy () {
+    this.unsubscribePlaylistChanges()
+  }
 
-    if (this.lazyLoad !== true) this.load()
+  disableForReuse () {
+    this.disabled = true
+  }
+
+  enabledForReuse () {
+    this.disabled = false
   }
 
   reload () {
+    logger('Reloading component')
+
     this.videoPlaylists = []
     this.videoPlaylistSearch = undefined
 
-    this.init()
+    this.resetOptions(true)
+    this.load()
 
     this.cd.markForCheck()
   }
 
   load () {
-    forkJoin([
-      this.videoPlaylistService.listAccountPlaylists(this.user.account, undefined, '-updatedAt', this.videoPlaylistSearch),
-      this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
-    ])
-      .subscribe(
-        ([ playlistsResult, existResult ]) => {
-          this.videoPlaylists = []
-          for (const playlist of playlistsResult.data) {
-            const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id)
-
-            this.videoPlaylists.push({
-              id: playlist.id,
-              displayName: playlist.displayName,
-              inPlaylist: !!existingPlaylist,
-              playlistElementId:  existingPlaylist ? existingPlaylist.playlistElementId : undefined,
-              startTimestamp: existingPlaylist ? existingPlaylist.startTimestamp : undefined,
-              stopTimestamp: existingPlaylist ? existingPlaylist.stopTimestamp : undefined
-            })
-          }
-
-          this.cd.markForCheck()
-        }
-      )
+    logger('Loading component')
+
+    this.listenToPlaylistChanges()
+
+    this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch)
+        .subscribe(playlistsResult => {
+          this.playlistsData = playlistsResult.data
+
+          this.videoPlaylistService.runPlaylistCheck(this.video.id)
+        })
   }
 
   openChange (opened: boolean) {
@@ -154,13 +167,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
     }
 
     this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe(
-      res => {
-        this.videoPlaylists.push({
-          id: res.videoPlaylist.id,
-          displayName,
-          inPlaylist: false
-        })
-
+      () => {
         this.isNewPlaylistBlockOpened = false
 
         this.cd.markForCheck()
@@ -197,25 +204,57 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
   private removeVideoFromPlaylist (playlist: PlaylistSummary) {
     if (!playlist.playlistElementId) return
 
-    this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, playlist.playlistElementId)
+    this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, playlist.playlistElementId, this.video.id)
         .subscribe(
           () => {
             this.notifier.success(this.i18n('Video removed from {{name}}', { name: playlist.displayName }))
-
-            playlist.inPlaylist = false
-            playlist.playlistElementId = undefined
           },
 
           err => {
             this.notifier.error(err.message)
-
-            playlist.inPlaylist = true
           },
 
           () => this.cd.markForCheck()
         )
   }
 
+  private listenToPlaylistChanges () {
+    this.unsubscribePlaylistChanges()
+
+    this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id)
+                                         .pipe(filter(() => this.disabled === false))
+                                         .subscribe(existResult => this.rebuildPlaylists(existResult))
+  }
+
+  private unsubscribePlaylistChanges () {
+    if (this.listenToPlaylistChangeSub) {
+      this.listenToPlaylistChangeSub.unsubscribe()
+      this.listenToPlaylistChangeSub = undefined
+    }
+  }
+
+  private rebuildPlaylists (existResult: VideoExistInPlaylist[]) {
+    logger('Got existing results for %d.', this.video.id, existResult)
+
+    this.videoPlaylists = []
+    for (const playlist of this.playlistsData) {
+      const existingPlaylist = existResult.find(p => p.playlistId === playlist.id)
+
+      this.videoPlaylists.push({
+        id: playlist.id,
+        displayName: playlist.displayName,
+        inPlaylist: !!existingPlaylist,
+        playlistElementId: existingPlaylist ? existingPlaylist.playlistElementId : undefined,
+        startTimestamp: existingPlaylist ? existingPlaylist.startTimestamp : undefined,
+        stopTimestamp: existingPlaylist ? existingPlaylist.stopTimestamp : undefined
+      })
+    }
+
+    logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
+
+    this.cd.markForCheck()
+  }
+
   private addVideoInPlaylist (playlist: PlaylistSummary) {
     const body: VideoPlaylistElementCreate = { videoId: this.video.id }
 
@@ -224,13 +263,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
 
     this.videoPlaylistService.addVideoInPlaylist(playlist.id, body)
       .subscribe(
-        res => {
-          playlist.inPlaylist = true
-          playlist.playlistElementId = res.videoPlaylistElement.id
-
-          playlist.startTimestamp = body.startTimestamp
-          playlist.stopTimestamp = body.stopTimestamp
-
+        () => {
           const message = body.startTimestamp || body.stopTimestamp
             ? this.i18n('Video added in {{n}} at timestamps {{t}}', { n: playlist.displayName, t: this.formatTimestamp(playlist) })
             : this.i18n('Video added in {{n}}', { n: playlist.displayName })
@@ -240,8 +273,6 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
 
         err => {
           this.notifier.error(err.message)
-
-          playlist.inPlaylist = false
         },
 
         () => this.cd.markForCheck()
index bb2fe7da38df5dda65febdadbb7b5df5edfc2c6c..4864581b5649c4ecb1697aba40eec149e0315786 100644 (file)
@@ -96,7 +96,9 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
   }
 
   removeFromPlaylist (playlistElement: VideoPlaylistElement) {
-    this.videoPlaylistService.removeVideoFromPlaylist(this.playlist.id, playlistElement.id)
+    const videoId = this.playlistElement.video ? this.playlistElement.video.id : undefined
+
+    this.videoPlaylistService.removeVideoFromPlaylist(this.playlist.id, playlistElement.id, videoId)
         .subscribe(
           () => {
             this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName }))
@@ -116,7 +118,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
     body.startTimestamp = this.timestampOptions.startTimestampEnabled ? this.timestampOptions.startTimestamp : null
     body.stopTimestamp = this.timestampOptions.stopTimestampEnabled ? this.timestampOptions.stopTimestamp : null
 
-    this.videoPlaylistService.updateVideoOfPlaylist(this.playlist.id, playlistElement.id, body)
+    this.videoPlaylistService.updateVideoOfPlaylist(this.playlist.id, playlistElement.id, body, this.playlistElement.video.id)
         .subscribe(
           () => {
             this.notifier.success(this.i18n('Timestamps updated'))
index d78fdc09f062e8aea90ef292cff3441adeabbe32..c5b87fc1165681ddf13192d67a1b86042af34227 100644 (file)
@@ -1,6 +1,6 @@
-import { bufferTime, catchError, distinctUntilChanged, filter, first, map, share, switchMap } from 'rxjs/operators'
+import { bufferTime, catchError, filter, map, share, switchMap, tap } from 'rxjs/operators'
 import { Injectable } from '@angular/core'
-import { Observable, ReplaySubject, Subject } from 'rxjs'
+import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
 import { RestExtractor } from '../rest/rest-extractor.service'
 import { HttpClient, HttpParams } from '@angular/common/http'
 import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared'
@@ -11,16 +11,22 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
 import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model'
 import { objectToFormData } from '@app/shared/misc/utils'
-import { ServerService } from '@app/core'
+import { AuthUser, ServerService } from '@app/core'
 import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { AccountService } from '@app/shared/account/account.service'
 import { Account } from '@app/shared/account/account.model'
 import { RestService } from '@app/shared/rest'
-import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
+import { VideoExistInPlaylist, VideosExistInPlaylists } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
 import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model'
 import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
 import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model'
 import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
+import { uniq } from 'lodash-es'
+import * as debug from 'debug'
+
+const logger = debug('peertube:playlists:VideoPlaylistService')
+
+type CachedPlaylist = VideoPlaylist | { id: number, displayName: string }
 
 @Injectable()
 export class VideoPlaylistService {
@@ -28,8 +34,15 @@ export class VideoPlaylistService {
   static MY_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/users/me/video-playlists/'
 
   // Use a replay subject because we "next" a value before subscribing
-  private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1)
-  private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist>
+  private videoExistsInPlaylistNotifier = new ReplaySubject<number>(1)
+  private videoExistsInPlaylistCacheSubject = new Subject<VideosExistInPlaylists>()
+  private readonly videoExistsInPlaylistObservable: Observable<VideosExistInPlaylists>
+
+  private videoExistsObservableCache: { [ id: number ]: Observable<VideoExistInPlaylist[]> } = {}
+  private videoExistsCache: { [ id: number ]: VideoExistInPlaylist[] } = {}
+
+  private myAccountPlaylistCache: ResultList<CachedPlaylist> = undefined
+  private myAccountPlaylistCacheSubject = new Subject<ResultList<CachedPlaylist>>()
 
   constructor (
     private authHttp: HttpClient,
@@ -37,12 +50,16 @@ export class VideoPlaylistService {
     private restExtractor: RestExtractor,
     private restService: RestService
   ) {
-    this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe(
-      distinctUntilChanged(),
-      bufferTime(500),
-      filter(videoIds => videoIds.length !== 0),
-      switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
-      share()
+    this.videoExistsInPlaylistObservable = merge(
+      this.videoExistsInPlaylistNotifier.pipe(
+        bufferTime(500),
+        filter(videoIds => videoIds.length !== 0),
+        map(videoIds => uniq(videoIds)),
+        switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
+        share()
+      ),
+
+      this.videoExistsInPlaylistCacheSubject
     )
   }
 
@@ -60,6 +77,17 @@ export class VideoPlaylistService {
                )
   }
 
+  listMyPlaylistWithCache (user: AuthUser, search?: string) {
+    if (!search && this.myAccountPlaylistCache) return of(this.myAccountPlaylistCache)
+
+    return this.listAccountPlaylists(user.account, undefined, '-updatedAt', search)
+               .pipe(
+                 tap(result => {
+                   if (!search) this.myAccountPlaylistCache = result
+                 })
+               )
+  }
+
   listAccountPlaylists (
     account: Account,
     componentPagination: ComponentPagination,
@@ -97,6 +125,16 @@ export class VideoPlaylistService {
 
     return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data)
                .pipe(
+                 tap(res => {
+                   this.myAccountPlaylistCache.total++
+
+                   this.myAccountPlaylistCache.data.push({
+                     id: res.videoPlaylist.id,
+                     displayName: body.displayName
+                   })
+
+                   this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -107,6 +145,12 @@ export class VideoPlaylistService {
     return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id, data)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   const playlist = this.myAccountPlaylistCache.data.find(p => p.id === videoPlaylist.id)
+                   playlist.displayName = body.displayName
+
+                   this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -115,6 +159,13 @@ export class VideoPlaylistService {
     return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   this.myAccountPlaylistCache.total--
+                   this.myAccountPlaylistCache.data = this.myAccountPlaylistCache.data
+                                                          .filter(p => p.id !== videoPlaylist.id)
+
+                   this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -123,21 +174,49 @@ export class VideoPlaylistService {
     const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos'
 
     return this.authHttp.post<{ videoPlaylistElement: { id: number } }>(url, body)
-               .pipe(catchError(err => this.restExtractor.handleError(err)))
+               .pipe(
+                 tap(res => {
+                   const existsResult = this.videoExistsCache[body.videoId]
+                   existsResult.push({
+                     playlistId,
+                     playlistElementId: res.videoPlaylistElement.id,
+                     startTimestamp: body.startTimestamp,
+                     stopTimestamp: body.stopTimestamp
+                   })
+
+                   this.runPlaylistCheck(body.videoId)
+                 }),
+                 catchError(err => this.restExtractor.handleError(err))
+               )
   }
 
-  updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate) {
+  updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate, videoId: number) {
     return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId, body)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   const existsResult = this.videoExistsCache[videoId]
+                   const elem = existsResult.find(e => e.playlistElementId === playlistElementId)
+
+                   elem.startTimestamp = body.startTimestamp
+                   elem.stopTimestamp = body.stopTimestamp
+
+                   this.runPlaylistCheck(videoId)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
 
-  removeVideoFromPlaylist (playlistId: number, playlistElementId: number) {
+  removeVideoFromPlaylist (playlistId: number, playlistElementId: number, videoId?: number) {
     return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   if (!videoId) return
+
+                   this.videoExistsCache[videoId] = this.videoExistsCache[videoId].filter(e => e.playlistElementId !== playlistElementId)
+                   this.runPlaylistCheck(videoId)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -173,10 +252,37 @@ export class VideoPlaylistService {
                )
   }
 
-  doesVideoExistInPlaylist (videoId: number) {
-    this.videoExistsInPlaylistSubject.next(videoId)
+  listenToMyAccountPlaylistsChange () {
+    return this.myAccountPlaylistCacheSubject.asObservable()
+  }
+
+  listenToVideoPlaylistChange (videoId: number) {
+    if (this.videoExistsObservableCache[ videoId ]) {
+      return this.videoExistsObservableCache[ videoId ]
+    }
+
+    const obs = this.videoExistsInPlaylistObservable
+                    .pipe(
+                      map(existsResult => existsResult[ videoId ]),
+                      filter(r => !!r),
+                      tap(result => this.videoExistsCache[ videoId ] = result)
+                    )
+
+    this.videoExistsObservableCache[ videoId ] = obs
+    return obs
+  }
+
+  runPlaylistCheck (videoId: number) {
+    logger('Running playlist check.')
+
+    if (this.videoExistsCache[videoId]) {
+      logger('Found cache for %d.', videoId)
+
+      return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[videoId] })
+    }
 
-    return this.videoExistsInPlaylistObservable.pipe(first())
+    logger('Fetching from network for %d.', videoId)
+    return this.videoExistsInPlaylistNotifier.next(videoId)
   }
 
   extractPlaylists (result: ResultList<VideoPlaylistServerModel>) {
@@ -218,7 +324,7 @@ export class VideoPlaylistService {
                )
   }
 
-  private doVideosExistInPlaylist (videoIds: number[]): Observable<VideoExistInPlaylist> {
+  private doVideosExistInPlaylist (videoIds: number[]): Observable<VideosExistInPlaylists> {
     const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist'
 
     let params = new HttpParams()
index a603f87e5b59b5ff451ec606c8723954ac2774da..598a7a98371e6225441583284f30545239a6f162 100644 (file)
@@ -17,8 +17,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
 import { ScreenService } from '@app/shared/misc/screen.service'
 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
-import { forkJoin } from 'rxjs'
-import { first } from 'rxjs/operators'
+import { switchMap } from 'rxjs/operators'
 
 export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
 export type MiniatureDisplayOptions = {
@@ -193,7 +192,7 @@ export class VideoMiniatureComponent implements OnInit {
   }
 
   removeFromWatchLater () {
-    this.videoPlaylistService.removeVideoFromPlaylist(this.watchLaterPlaylist.id, this.watchLaterPlaylist.playlistElementId)
+    this.videoPlaylistService.removeVideoFromPlaylist(this.watchLaterPlaylist.id, this.watchLaterPlaylist.playlistElementId, this.video.id)
         .subscribe(
           _ => { /* empty */ }
         )
@@ -222,27 +221,27 @@ export class VideoMiniatureComponent implements OnInit {
   }
 
   private loadWatchLater () {
-    if (!this.isUserLoggedIn()) return
-
-    forkJoin([
-      this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id),
-      this.authService.userInformationLoaded.pipe(first())
-    ]).subscribe(
-      ([ existResult ]) => {
-        const watchLaterPlaylist = this.authService.getUser().specialPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER)
-        const existsInWatchLater = existResult[ this.video.id ].find(r => r.playlistId === watchLaterPlaylist.id)
-        this.inWatchLaterPlaylist = false
-
-        this.watchLaterPlaylist = {
-          id: watchLaterPlaylist.id
-        }
-
-        if (existsInWatchLater) {
-          this.inWatchLaterPlaylist = true
-          this.watchLaterPlaylist.playlistElementId = existsInWatchLater.playlistElementId
-        }
-
-        this.cd.markForCheck()
-      })
+    if (!this.isUserLoggedIn() || this.inWatchLaterPlaylist !== undefined) return
+
+    this.authService.userInformationLoaded
+        .pipe(switchMap(() => this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id)))
+        .subscribe(existResult => {
+          const watchLaterPlaylist = this.authService.getUser().specialPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER)
+          const existsInWatchLater = existResult.find(r => r.playlistId === watchLaterPlaylist.id)
+          this.inWatchLaterPlaylist = false
+
+          this.watchLaterPlaylist = {
+            id: watchLaterPlaylist.id
+          }
+
+          if (existsInWatchLater) {
+            this.inWatchLaterPlaylist = true
+            this.watchLaterPlaylist.playlistElementId = existsInWatchLater.playlistElementId
+          }
+
+          this.cd.markForCheck()
+        })
+
+    this.videoPlaylistService.runPlaylistCheck(this.video.id)
   }
 }
index c30a435570116c81ed51cb78e92c9da9cef21ebc..7e71a390b668c0134f7e132fa352819cc4f1b9f8 100644 (file)
@@ -2,7 +2,7 @@
   [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name"
   class="video-thumbnail"
 >
-  <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" loading="lazy" />
+  <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" />
 
   <div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay">
     <ng-container *ngIf="inWatchLaterPlaylist !== true">
index 17bf338f70282dc25852c08fa2d73b8400c4f263..51e096fc8c3e58e210723e86d19802114e39a29f 100644 (file)
   resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.2.tgz#d4c25420044d4a5b65e00a82fc04b7824b62691f"
   integrity sha512-+NPqjXgyA02xTHKJDeDca9u8Zr42ts6jhdND4C3PrPeQ35RJa0dmfAedXW7a9K4N1QcBbuWI1nSfGK4r1eVFCQ==
 
+"@types/debug@^4.1.5":
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
+  integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
+
 "@types/events@*":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
index 735a3cbee3b01d6003ff9ba1731f7a6e3f2ad9bb..d0bd9946323b16066006c87f1f91f7d69099674d 100644 (file)
@@ -2,7 +2,7 @@ import * as express from 'express'
 import { asyncMiddleware, authenticate } from '../../../middlewares'
 import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists'
 import { VideoPlaylistModel } from '../../../models/video/video-playlist'
-import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
+import { VideosExistInPlaylists } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
 
 const myVideoPlaylistsRouter = express.Router()
 
@@ -26,7 +26,7 @@ async function doVideosInPlaylistExist (req: express.Request, res: express.Respo
 
   const results = await VideoPlaylistModel.listPlaylistIdsOf(user.Account.id, videoIds)
 
-  const existObject: VideoExistInPlaylist = {}
+  const existObject: VideosExistInPlaylists = {}
 
   for (const videoId of videoIds) {
     existObject[videoId] = []
index 328b69df61df185cfcd2dcf2826e1793b03e8185..16885119674ba78f248c227c874f0591f9045344 100644 (file)
@@ -57,5 +57,3 @@ export interface MyUserSpecialPlaylist {
 export interface MyUser extends User {
   specialPlaylists: MyUserSpecialPlaylist[]
 }
-
-
index 1b57257e2a411078d4b6055b249a219fdd6e2dd2..fc979c8c0be90b475cb5515b807a502d7dc55073 100644 (file)
@@ -1,8 +1,10 @@
+export type VideosExistInPlaylists = {
+  [videoId: number ]: VideoExistInPlaylist[]
+}
+
 export type VideoExistInPlaylist = {
-  [videoId: number ]: {
-    playlistElementId: number
-    playlistId: number
-    startTimestamp?: number
-    stopTimestamp?: number
-  }[]
+  playlistElementId: number
+  playlistId: number
+  startTimestamp?: number
+  stopTimestamp?: number
 }