aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/video-playlist/video-playlist.service.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-01-06 13:06:13 +0100
committerChocobozzz <me@florianbigard.com>2020-01-06 13:34:08 +0100
commit51b34a11b298b466faef9c8d24beec4442d7add3 (patch)
tree57f9969eb5eb31f9b7cd8f1185927b105a3de809 /client/src/app/shared/video-playlist/video-playlist.service.ts
parent46db9430af70f45bc656cb0ac8e519f5d0be0149 (diff)
downloadPeerTube-51b34a11b298b466faef9c8d24beec4442d7add3.tar.gz
PeerTube-51b34a11b298b466faef9c8d24beec4442d7add3.tar.zst
PeerTube-51b34a11b298b466faef9c8d24beec4442d7add3.zip
Share playlists state
Diffstat (limited to 'client/src/app/shared/video-playlist/video-playlist.service.ts')
-rw-r--r--client/src/app/shared/video-playlist/video-playlist.service.ts144
1 files changed, 125 insertions, 19 deletions
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts
index d78fdc09f..c5b87fc11 100644
--- a/client/src/app/shared/video-playlist/video-playlist.service.ts
+++ b/client/src/app/shared/video-playlist/video-playlist.service.ts
@@ -1,6 +1,6 @@
1import { bufferTime, catchError, distinctUntilChanged, filter, first, map, share, switchMap } from 'rxjs/operators' 1import { bufferTime, catchError, filter, map, share, switchMap, tap } from 'rxjs/operators'
2import { Injectable } from '@angular/core' 2import { Injectable } from '@angular/core'
3import { Observable, ReplaySubject, Subject } from 'rxjs' 3import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
4import { RestExtractor } from '../rest/rest-extractor.service' 4import { RestExtractor } from '../rest/rest-extractor.service'
5import { HttpClient, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpParams } from '@angular/common/http'
6import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared' 6import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared'
@@ -11,16 +11,22 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
11import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' 11import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
12import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' 12import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model'
13import { objectToFormData } from '@app/shared/misc/utils' 13import { objectToFormData } from '@app/shared/misc/utils'
14import { ServerService } from '@app/core' 14import { AuthUser, ServerService } from '@app/core'
15import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 15import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
16import { AccountService } from '@app/shared/account/account.service' 16import { AccountService } from '@app/shared/account/account.service'
17import { Account } from '@app/shared/account/account.model' 17import { Account } from '@app/shared/account/account.model'
18import { RestService } from '@app/shared/rest' 18import { RestService } from '@app/shared/rest'
19import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' 19import { VideoExistInPlaylist, VideosExistInPlaylists } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
20import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model' 20import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model'
21import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 21import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
22import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model' 22import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model'
23import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' 23import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
24import { uniq } from 'lodash-es'
25import * as debug from 'debug'
26
27const logger = debug('peertube:playlists:VideoPlaylistService')
28
29type CachedPlaylist = VideoPlaylist | { id: number, displayName: string }
24 30
25@Injectable() 31@Injectable()
26export class VideoPlaylistService { 32export class VideoPlaylistService {
@@ -28,8 +34,15 @@ export class VideoPlaylistService {
28 static MY_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/users/me/video-playlists/' 34 static MY_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/users/me/video-playlists/'
29 35
30 // Use a replay subject because we "next" a value before subscribing 36 // Use a replay subject because we "next" a value before subscribing
31 private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) 37 private videoExistsInPlaylistNotifier = new ReplaySubject<number>(1)
32 private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> 38 private videoExistsInPlaylistCacheSubject = new Subject<VideosExistInPlaylists>()
39 private readonly videoExistsInPlaylistObservable: Observable<VideosExistInPlaylists>
40
41 private videoExistsObservableCache: { [ id: number ]: Observable<VideoExistInPlaylist[]> } = {}
42 private videoExistsCache: { [ id: number ]: VideoExistInPlaylist[] } = {}
43
44 private myAccountPlaylistCache: ResultList<CachedPlaylist> = undefined
45 private myAccountPlaylistCacheSubject = new Subject<ResultList<CachedPlaylist>>()
33 46
34 constructor ( 47 constructor (
35 private authHttp: HttpClient, 48 private authHttp: HttpClient,
@@ -37,12 +50,16 @@ export class VideoPlaylistService {
37 private restExtractor: RestExtractor, 50 private restExtractor: RestExtractor,
38 private restService: RestService 51 private restService: RestService
39 ) { 52 ) {
40 this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe( 53 this.videoExistsInPlaylistObservable = merge(
41 distinctUntilChanged(), 54 this.videoExistsInPlaylistNotifier.pipe(
42 bufferTime(500), 55 bufferTime(500),
43 filter(videoIds => videoIds.length !== 0), 56 filter(videoIds => videoIds.length !== 0),
44 switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)), 57 map(videoIds => uniq(videoIds)),
45 share() 58 switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
59 share()
60 ),
61
62 this.videoExistsInPlaylistCacheSubject
46 ) 63 )
47 } 64 }
48 65
@@ -60,6 +77,17 @@ export class VideoPlaylistService {
60 ) 77 )
61 } 78 }
62 79
80 listMyPlaylistWithCache (user: AuthUser, search?: string) {
81 if (!search && this.myAccountPlaylistCache) return of(this.myAccountPlaylistCache)
82
83 return this.listAccountPlaylists(user.account, undefined, '-updatedAt', search)
84 .pipe(
85 tap(result => {
86 if (!search) this.myAccountPlaylistCache = result
87 })
88 )
89 }
90
63 listAccountPlaylists ( 91 listAccountPlaylists (
64 account: Account, 92 account: Account,
65 componentPagination: ComponentPagination, 93 componentPagination: ComponentPagination,
@@ -97,6 +125,16 @@ export class VideoPlaylistService {
97 125
98 return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) 126 return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data)
99 .pipe( 127 .pipe(
128 tap(res => {
129 this.myAccountPlaylistCache.total++
130
131 this.myAccountPlaylistCache.data.push({
132 id: res.videoPlaylist.id,
133 displayName: body.displayName
134 })
135
136 this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
137 }),
100 catchError(err => this.restExtractor.handleError(err)) 138 catchError(err => this.restExtractor.handleError(err))
101 ) 139 )
102 } 140 }
@@ -107,6 +145,12 @@ export class VideoPlaylistService {
107 return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id, data) 145 return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id, data)
108 .pipe( 146 .pipe(
109 map(this.restExtractor.extractDataBool), 147 map(this.restExtractor.extractDataBool),
148 tap(() => {
149 const playlist = this.myAccountPlaylistCache.data.find(p => p.id === videoPlaylist.id)
150 playlist.displayName = body.displayName
151
152 this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
153 }),
110 catchError(err => this.restExtractor.handleError(err)) 154 catchError(err => this.restExtractor.handleError(err))
111 ) 155 )
112 } 156 }
@@ -115,6 +159,13 @@ export class VideoPlaylistService {
115 return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id) 159 return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id)
116 .pipe( 160 .pipe(
117 map(this.restExtractor.extractDataBool), 161 map(this.restExtractor.extractDataBool),
162 tap(() => {
163 this.myAccountPlaylistCache.total--
164 this.myAccountPlaylistCache.data = this.myAccountPlaylistCache.data
165 .filter(p => p.id !== videoPlaylist.id)
166
167 this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
168 }),
118 catchError(err => this.restExtractor.handleError(err)) 169 catchError(err => this.restExtractor.handleError(err))
119 ) 170 )
120 } 171 }
@@ -123,21 +174,49 @@ export class VideoPlaylistService {
123 const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos' 174 const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos'
124 175
125 return this.authHttp.post<{ videoPlaylistElement: { id: number } }>(url, body) 176 return this.authHttp.post<{ videoPlaylistElement: { id: number } }>(url, body)
126 .pipe(catchError(err => this.restExtractor.handleError(err))) 177 .pipe(
178 tap(res => {
179 const existsResult = this.videoExistsCache[body.videoId]
180 existsResult.push({
181 playlistId,
182 playlistElementId: res.videoPlaylistElement.id,
183 startTimestamp: body.startTimestamp,
184 stopTimestamp: body.stopTimestamp
185 })
186
187 this.runPlaylistCheck(body.videoId)
188 }),
189 catchError(err => this.restExtractor.handleError(err))
190 )
127 } 191 }
128 192
129 updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate) { 193 updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate, videoId: number) {
130 return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId, body) 194 return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId, body)
131 .pipe( 195 .pipe(
132 map(this.restExtractor.extractDataBool), 196 map(this.restExtractor.extractDataBool),
197 tap(() => {
198 const existsResult = this.videoExistsCache[videoId]
199 const elem = existsResult.find(e => e.playlistElementId === playlistElementId)
200
201 elem.startTimestamp = body.startTimestamp
202 elem.stopTimestamp = body.stopTimestamp
203
204 this.runPlaylistCheck(videoId)
205 }),
133 catchError(err => this.restExtractor.handleError(err)) 206 catchError(err => this.restExtractor.handleError(err))
134 ) 207 )
135 } 208 }
136 209
137 removeVideoFromPlaylist (playlistId: number, playlistElementId: number) { 210 removeVideoFromPlaylist (playlistId: number, playlistElementId: number, videoId?: number) {
138 return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId) 211 return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId)
139 .pipe( 212 .pipe(
140 map(this.restExtractor.extractDataBool), 213 map(this.restExtractor.extractDataBool),
214 tap(() => {
215 if (!videoId) return
216
217 this.videoExistsCache[videoId] = this.videoExistsCache[videoId].filter(e => e.playlistElementId !== playlistElementId)
218 this.runPlaylistCheck(videoId)
219 }),
141 catchError(err => this.restExtractor.handleError(err)) 220 catchError(err => this.restExtractor.handleError(err))
142 ) 221 )
143 } 222 }
@@ -173,10 +252,37 @@ export class VideoPlaylistService {
173 ) 252 )
174 } 253 }
175 254
176 doesVideoExistInPlaylist (videoId: number) { 255 listenToMyAccountPlaylistsChange () {
177 this.videoExistsInPlaylistSubject.next(videoId) 256 return this.myAccountPlaylistCacheSubject.asObservable()
257 }
258
259 listenToVideoPlaylistChange (videoId: number) {
260 if (this.videoExistsObservableCache[ videoId ]) {
261 return this.videoExistsObservableCache[ videoId ]
262 }
263
264 const obs = this.videoExistsInPlaylistObservable
265 .pipe(
266 map(existsResult => existsResult[ videoId ]),
267 filter(r => !!r),
268 tap(result => this.videoExistsCache[ videoId ] = result)
269 )
270
271 this.videoExistsObservableCache[ videoId ] = obs
272 return obs
273 }
274
275 runPlaylistCheck (videoId: number) {
276 logger('Running playlist check.')
277
278 if (this.videoExistsCache[videoId]) {
279 logger('Found cache for %d.', videoId)
280
281 return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[videoId] })
282 }
178 283
179 return this.videoExistsInPlaylistObservable.pipe(first()) 284 logger('Fetching from network for %d.', videoId)
285 return this.videoExistsInPlaylistNotifier.next(videoId)
180 } 286 }
181 287
182 extractPlaylists (result: ResultList<VideoPlaylistServerModel>) { 288 extractPlaylists (result: ResultList<VideoPlaylistServerModel>) {
@@ -218,7 +324,7 @@ export class VideoPlaylistService {
218 ) 324 )
219 } 325 }
220 326
221 private doVideosExistInPlaylist (videoIds: number[]): Observable<VideoExistInPlaylist> { 327 private doVideosExistInPlaylist (videoIds: number[]): Observable<VideosExistInPlaylists> {
222 const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist' 328 const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist'
223 329
224 let params = new HttpParams() 330 let params = new HttpParams()