Add to playlist dropdown
[oweals/peertube.git] / client / src / app / shared / video / video.service.ts
1 import { catchError, map, switchMap } from 'rxjs/operators'
2 import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
3 import { Injectable } from '@angular/core'
4 import { Observable } from 'rxjs'
5 import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared'
6 import { ResultList } from '../../../../../shared/models/result-list.model'
7 import {
8   UserVideoRate,
9   UserVideoRateType,
10   UserVideoRateUpdate,
11   VideoConstant,
12   VideoFilter,
13   VideoPrivacy,
14   VideoUpdate
15 } from '../../../../../shared/models/videos'
16 import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum'
17 import { environment } from '../../../environments/environment'
18 import { ComponentPagination } from '../rest/component-pagination.model'
19 import { RestExtractor } from '../rest/rest-extractor.service'
20 import { RestService } from '../rest/rest.service'
21 import { UserService } from '../users/user.service'
22 import { VideoSortField } from './sort-field.type'
23 import { VideoDetails } from './video-details.model'
24 import { VideoEdit } from './video-edit.model'
25 import { Video } from './video.model'
26 import { objectToFormData } from '@app/shared/misc/utils'
27 import { Account } from '@app/shared/account/account.model'
28 import { AccountService } from '@app/shared/account/account.service'
29 import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
30 import { ServerService } from '@app/core'
31 import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
32 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
33 import { I18n } from '@ngx-translate/i18n-polyfill'
34 import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
35 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
36
37 export interface VideosProvider {
38   getVideos (
39     videoPagination: ComponentPagination,
40     sort: VideoSortField,
41     filter?: VideoFilter,
42     categoryOneOf?: number
43   ): Observable<{ videos: Video[], totalVideos: number }>
44 }
45
46 @Injectable()
47 export class VideoService implements VideosProvider {
48   static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
49   static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
50
51   constructor (
52     private authHttp: HttpClient,
53     private restExtractor: RestExtractor,
54     private restService: RestService,
55     private serverService: ServerService,
56     private i18n: I18n
57   ) {}
58
59   getVideoViewUrl (uuid: string) {
60     return VideoService.BASE_VIDEO_URL + uuid + '/views'
61   }
62
63   getUserWatchingVideoUrl (uuid: string) {
64     return VideoService.BASE_VIDEO_URL + uuid + '/watching'
65   }
66
67   getVideo (uuid: string): Observable<VideoDetails> {
68     return this.serverService.localeObservable
69                .pipe(
70                  switchMap(translations => {
71                    return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + uuid)
72                               .pipe(map(videoHash => ({ videoHash, translations })))
73                  }),
74                  map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)),
75                  catchError(err => this.restExtractor.handleError(err))
76                )
77   }
78
79   updateVideo (video: VideoEdit) {
80     const language = video.language || null
81     const licence = video.licence || null
82     const category = video.category || null
83     const description = video.description || null
84     const support = video.support || null
85     const scheduleUpdate = video.scheduleUpdate || null
86     const originallyPublishedAt = video.originallyPublishedAt || null
87
88     const body: VideoUpdate = {
89       name: video.name,
90       category,
91       licence,
92       language,
93       support,
94       description,
95       channelId: video.channelId,
96       privacy: video.privacy,
97       tags: video.tags,
98       nsfw: video.nsfw,
99       waitTranscoding: video.waitTranscoding,
100       commentsEnabled: video.commentsEnabled,
101       downloadEnabled: video.downloadEnabled,
102       thumbnailfile: video.thumbnailfile,
103       previewfile: video.previewfile,
104       scheduleUpdate,
105       originallyPublishedAt
106     }
107
108     const data = objectToFormData(body)
109
110     return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, data)
111                .pipe(
112                  map(this.restExtractor.extractDataBool),
113                  catchError(err => this.restExtractor.handleError(err))
114                )
115   }
116
117   uploadVideo (video: FormData) {
118     const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
119
120     return this.authHttp
121                .request<{ video: { id: number, uuid: string } }>(req)
122                .pipe(catchError(err => this.restExtractor.handleError(err)))
123   }
124
125   getMyVideos (videoPagination: ComponentPagination, sort: VideoSortField): Observable<{ videos: Video[], totalVideos: number }> {
126     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
127
128     let params = new HttpParams()
129     params = this.restService.addRestGetParams(params, pagination, sort)
130
131     return this.authHttp
132                .get<ResultList<Video>>(UserService.BASE_USERS_URL + '/me/videos', { params })
133                .pipe(
134                  switchMap(res => this.extractVideos(res)),
135                  catchError(err => this.restExtractor.handleError(err))
136                )
137   }
138
139   getAccountVideos (
140     account: Account,
141     videoPagination: ComponentPagination,
142     sort: VideoSortField
143   ): Observable<{ videos: Video[], totalVideos: number }> {
144     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
145
146     let params = new HttpParams()
147     params = this.restService.addRestGetParams(params, pagination, sort)
148
149     return this.authHttp
150                .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params })
151                .pipe(
152                  switchMap(res => this.extractVideos(res)),
153                  catchError(err => this.restExtractor.handleError(err))
154                )
155   }
156
157   getVideoChannelVideos (
158     videoChannel: VideoChannel,
159     videoPagination: ComponentPagination,
160     sort: VideoSortField
161   ): Observable<{ videos: Video[], totalVideos: number }> {
162     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
163
164     let params = new HttpParams()
165     params = this.restService.addRestGetParams(params, pagination, sort)
166
167     return this.authHttp
168                .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params })
169                .pipe(
170                  switchMap(res => this.extractVideos(res)),
171                  catchError(err => this.restExtractor.handleError(err))
172                )
173   }
174
175   getPlaylistVideos (
176     videoPlaylistId: number | string,
177     videoPagination: ComponentPagination
178   ): Observable<{ videos: Video[], totalVideos: number }> {
179     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
180
181     let params = new HttpParams()
182     params = this.restService.addRestGetParams(params, pagination)
183
184     return this.authHttp
185                .get<ResultList<Video>>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylistId + '/videos', { params })
186                .pipe(
187                  switchMap(res => this.extractVideos(res)),
188                  catchError(err => this.restExtractor.handleError(err))
189                )
190   }
191
192   getUserSubscriptionVideos (
193     videoPagination: ComponentPagination,
194     sort: VideoSortField
195   ): Observable<{ videos: Video[], totalVideos: number }> {
196     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
197
198     let params = new HttpParams()
199     params = this.restService.addRestGetParams(params, pagination, sort)
200
201     return this.authHttp
202                .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params })
203                .pipe(
204                  switchMap(res => this.extractVideos(res)),
205                  catchError(err => this.restExtractor.handleError(err))
206                )
207   }
208
209   getVideos (
210     videoPagination: ComponentPagination,
211     sort: VideoSortField,
212     filter?: VideoFilter,
213     categoryOneOf?: number
214   ): Observable<{ videos: Video[], totalVideos: number }> {
215     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
216
217     let params = new HttpParams()
218     params = this.restService.addRestGetParams(params, pagination, sort)
219
220     if (filter) {
221       params = params.set('filter', filter)
222     }
223
224     if (categoryOneOf) {
225       params = params.set('categoryOneOf', categoryOneOf + '')
226     }
227
228     return this.authHttp
229                .get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
230                .pipe(
231                  switchMap(res => this.extractVideos(res)),
232                  catchError(err => this.restExtractor.handleError(err))
233                )
234   }
235
236   buildBaseFeedUrls (params: HttpParams) {
237     const feeds = [
238       {
239         format: FeedFormat.RSS,
240         label: 'rss 2.0',
241         url: VideoService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase()
242       },
243       {
244         format: FeedFormat.ATOM,
245         label: 'atom 1.0',
246         url: VideoService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase()
247       },
248       {
249         format: FeedFormat.JSON,
250         label: 'json 1.0',
251         url: VideoService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase()
252       }
253     ]
254
255     if (params && params.keys().length !== 0) {
256       for (const feed of feeds) {
257         feed.url += '?' + params.toString()
258       }
259     }
260
261     return feeds
262   }
263
264   getVideoFeedUrls (sort: VideoSortField, filter?: VideoFilter, categoryOneOf?: number) {
265     let params = this.restService.addRestGetParams(new HttpParams(), undefined, sort)
266
267     if (filter) params = params.set('filter', filter)
268
269     if (categoryOneOf) params = params.set('categoryOneOf', categoryOneOf + '')
270
271     return this.buildBaseFeedUrls(params)
272   }
273
274   getAccountFeedUrls (accountId: number) {
275     let params = this.restService.addRestGetParams(new HttpParams())
276     params = params.set('accountId', accountId.toString())
277
278     return this.buildBaseFeedUrls(params)
279   }
280
281   getVideoChannelFeedUrls (videoChannelId: number) {
282     let params = this.restService.addRestGetParams(new HttpParams())
283     params = params.set('videoChannelId', videoChannelId.toString())
284
285     return this.buildBaseFeedUrls(params)
286   }
287
288   removeVideo (id: number) {
289     return this.authHttp
290                .delete(VideoService.BASE_VIDEO_URL + id)
291                .pipe(
292                  map(this.restExtractor.extractDataBool),
293                  catchError(err => this.restExtractor.handleError(err))
294                )
295   }
296
297   loadCompleteDescription (descriptionPath: string) {
298     return this.authHttp
299                .get<{ description: string }>(environment.apiUrl + descriptionPath)
300                .pipe(
301                  map(res => res.description),
302                  catchError(err => this.restExtractor.handleError(err))
303                )
304   }
305
306   setVideoLike (id: number) {
307     return this.setVideoRate(id, 'like')
308   }
309
310   setVideoDislike (id: number) {
311     return this.setVideoRate(id, 'dislike')
312   }
313
314   unsetVideoLike (id: number) {
315     return this.setVideoRate(id, 'none')
316   }
317
318   getUserVideoRating (id: number) {
319     const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
320
321     return this.authHttp.get<UserVideoRate>(url)
322                .pipe(catchError(err => this.restExtractor.handleError(err)))
323   }
324
325   extractVideos (result: ResultList<VideoServerModel>) {
326     return this.serverService.localeObservable
327                .pipe(
328                  map(translations => {
329                    const videosJson = result.data
330                    const totalVideos = result.total
331                    const videos: Video[] = []
332
333                    for (const videoJson of videosJson) {
334                      videos.push(new Video(videoJson, translations))
335                    }
336
337                    return { videos, totalVideos }
338                  })
339                )
340   }
341
342   explainedPrivacyLabels (privacies: VideoConstant<VideoPrivacy>[]) {
343     const newPrivacies = privacies.slice()
344
345     const privatePrivacy = newPrivacies.find(p => p.id === VideoPrivacy.PRIVATE)
346     if (privatePrivacy) privatePrivacy.label = this.i18n('Only I can see this video')
347
348     const unlistedPrivacy = newPrivacies.find(p => p.id === VideoPrivacy.UNLISTED)
349     if (unlistedPrivacy) unlistedPrivacy.label = this.i18n('Only people with the private link can see this video')
350
351     const publicPrivacy = newPrivacies.find(p => p.id === VideoPrivacy.PUBLIC)
352     if (publicPrivacy) publicPrivacy.label = this.i18n('Anyone can see this video')
353
354     return privacies
355   }
356
357   private setVideoRate (id: number, rateType: UserVideoRateType) {
358     const url = VideoService.BASE_VIDEO_URL + id + '/rate'
359     const body: UserVideoRateUpdate = {
360       rating: rateType
361     }
362
363     return this.authHttp
364                .put(url, body)
365                .pipe(
366                  map(this.restExtractor.extractDataBool),
367                  catchError(err => this.restExtractor.handleError(err))
368                )
369   }
370 }