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