]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/shared/shared-main/video/video.service.ts
Add channel filters for my videos/followers
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-main / video / video.service.ts
CommitLineData
29510651
RK
1import { Observable } from 'rxjs'
2import { catchError, map, switchMap } from 'rxjs/operators'
3import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
202f6b6c 4import { Injectable } from '@angular/core'
29510651 5import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core'
67ed6552 6import { objectToFormData } from '@app/helpers'
8cd7faaa 7import {
dd24f1bb 8 BooleanBothQuery,
67ed6552
C
9 FeedFormat,
10 NSFWPolicyType,
11 ResultList,
8cd7faaa 12 UserVideoRate,
5c6d985f 13 UserVideoRateType,
8cd7faaa 14 UserVideoRateUpdate,
67ed6552 15 Video as VideoServerModel,
978c87e7 16 VideoChannel as VideoChannelServerModel,
8cd7faaa 17 VideoConstant,
67ed6552 18 VideoDetails as VideoDetailsServerModel,
66357162 19 VideoFileMetadata,
8cd7faaa
C
20 VideoFilter,
21 VideoPrivacy,
67ed6552 22 VideoSortField,
5beb89f2 23 VideoUpdate
67ed6552
C
24} from '@shared/models'
25import { environment } from '../../../../environments/environment'
b4c3c51d
C
26import { Account } from '../account/account.model'
27import { AccountService } from '../account/account.service'
67ed6552 28import { VideoChannel, VideoChannelService } from '../video-channel'
404b54e1
C
29import { VideoDetails } from './video-details.model'
30import { VideoEdit } from './video-edit.model'
202f6b6c 31import { Video } from './video.model'
dc8bc31b 32
dd24f1bb
C
33export type CommonVideoParams = {
34 videoPagination: ComponentPaginationLight
35 sort: VideoSortField
36 filter?: VideoFilter
37 categoryOneOf?: number[]
38 languageOneOf?: string[]
39 isLive?: boolean
40 skipCount?: boolean
41 // FIXME: remove?
42 nsfwPolicy?: NSFWPolicyType
43 nsfw?: BooleanBothQuery
7f5f4152
BJ
44}
45
dc8bc31b 46@Injectable()
dd24f1bb 47export class VideoService {
40e87e9e
C
48 static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
49 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
5beb89f2 50 static BASE_SUBSCRIPTION_FEEDS_URL = environment.apiUrl + '/feeds/subscriptions.'
dc8bc31b 51
df98563e 52 constructor (
d592e0a9 53 private authHttp: HttpClient,
de59c48f 54 private restExtractor: RestExtractor,
7ce44a74 55 private restService: RestService,
5beb89f2 56 private serverService: ServerService
4fd8aa32
C
57 ) {}
58
8cac1b64
C
59 getVideoViewUrl (uuid: string) {
60 return VideoService.BASE_VIDEO_URL + uuid + '/views'
61 }
62
6e46de09
C
63 getUserWatchingVideoUrl (uuid: string) {
64 return VideoService.BASE_VIDEO_URL + uuid + '/watching'
65 }
66
93cae479 67 getVideo (options: { videoId: string }): Observable<VideoDetails> {
ba430d75 68 return this.serverService.getServerLocale()
db400f44 69 .pipe(
7ce44a74 70 switchMap(translations => {
93cae479 71 return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + options.videoId)
74b7c6d4 72 .pipe(map(videoHash => ({ videoHash, translations })))
7ce44a74
C
73 }),
74 map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)),
e4f0e92e 75 catchError(err => this.restExtractor.handleError(err))
db400f44 76 )
4fd8aa32 77 }
dc8bc31b 78
404b54e1 79 updateVideo (video: VideoEdit) {
360329cc
C
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
e94fc297 85 const scheduleUpdate = video.scheduleUpdate || null
1e74f19a 86 const originallyPublishedAt = video.originallyPublishedAt || null
c24ac1c1 87
4771e000 88 const body: VideoUpdate = {
d8e689b8 89 name: video.name,
cadb46d8
C
90 category,
91 licence,
c24ac1c1 92 language,
3bf1ec2e 93 support,
cadb46d8 94 description,
0f320037 95 channelId: video.channelId,
fd45e8f4 96 privacy: video.privacy,
4771e000 97 tags: video.tags,
47564bbe 98 nsfw: video.nsfw,
2186386c 99 waitTranscoding: video.waitTranscoding,
6de36768 100 commentsEnabled: video.commentsEnabled,
7f2cfe3a 101 downloadEnabled: video.downloadEnabled,
6de36768 102 thumbnailfile: video.thumbnailfile,
bbe0f064 103 previewfile: video.previewfile,
7294aab0 104 pluginData: video.pluginData,
1e74f19a 105 scheduleUpdate,
106 originallyPublishedAt
df98563e 107 }
c24ac1c1 108
6de36768
C
109 const data = objectToFormData(body)
110
111 return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, data)
db400f44
C
112 .pipe(
113 map(this.restExtractor.extractDataBool),
e4f0e92e 114 catchError(err => this.restExtractor.handleError(err))
db400f44 115 )
d8e689b8
C
116 }
117
db7af09b 118 uploadVideo (video: FormData) {
334ddfa4 119 const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
bfb3a98f 120
fd45e8f4 121 return this.authHttp
2186386c 122 .request<{ video: { id: number, uuid: string } }>(req)
e4f0e92e 123 .pipe(catchError(err => this.restExtractor.handleError(err)))
bfb3a98f
C
124 }
125
978c87e7
C
126 getMyVideos (options: {
127 videoPagination: ComponentPaginationLight
128 sort: VideoSortField
129 userChannels?: VideoChannelServerModel[]
130 search?: string
131 }): Observable<ResultList<Video>> {
132 const { videoPagination, sort, userChannels = [], search } = options
133
4beda9e1 134 const pagination = this.restService.componentToRestPagination(videoPagination)
cf20596c 135
d592e0a9
C
136 let params = new HttpParams()
137 params = this.restService.addRestGetParams(params, pagination, sort)
1fd61899
C
138
139 if (search) {
140 const filters = this.restService.parseQueryStringFilter(search, {
141 isLive: {
142 prefix: 'isLive:',
1a7d0887 143 isBoolean: true
978c87e7
C
144 },
145 channelId: {
146 prefix: 'channel:',
147 handler: (name: string) => {
148 const channel = userChannels.find(c => c.name === name)
149
150 if (channel) return channel.id
151
152 return undefined
153 }
1fd61899
C
154 }
155 })
156
157 params = this.restService.addObjectParams(params, filters)
158 }
dc8bc31b 159
7ce44a74 160 return this.authHttp
bf64ed41 161 .get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
db400f44 162 .pipe(
7ce44a74 163 switchMap(res => this.extractVideos(res)),
e4f0e92e 164 catchError(err => this.restExtractor.handleError(err))
db400f44 165 )
dc8bc31b
C
166 }
167
dd24f1bb 168 getAccountVideos (parameters: CommonVideoParams & {
9df52d66 169 account: Pick<Account, 'nameWithHost'>
37024082 170 search?: string
0aa52e17 171 }): Observable<ResultList<Video>> {
dd24f1bb 172 const { account, search } = parameters
0626e7af
C
173
174 let params = new HttpParams()
dd24f1bb 175 params = this.buildCommonVideosParams({ params, ...parameters })
0aa52e17 176
dd24f1bb 177 if (search) params = params.set('search', search)
37024082 178
0626e7af 179 return this.authHttp
7ce44a74 180 .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params })
db400f44 181 .pipe(
7ce44a74 182 switchMap(res => this.extractVideos(res)),
e4f0e92e 183 catchError(err => this.restExtractor.handleError(err))
db400f44 184 )
0626e7af
C
185 }
186
dd24f1bb 187 getVideoChannelVideos (parameters: CommonVideoParams & {
9df52d66 188 videoChannel: Pick<VideoChannel, 'nameWithHost'>
0aa52e17 189 }): Observable<ResultList<Video>> {
dd24f1bb 190 const { videoChannel } = parameters
170726f5
C
191
192 let params = new HttpParams()
dd24f1bb 193 params = this.buildCommonVideosParams({ params, ...parameters })
0aa52e17 194
170726f5 195 return this.authHttp
f5b0af50 196 .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params })
db400f44 197 .pipe(
7ce44a74 198 switchMap(res => this.extractVideos(res)),
22a16e36
C
199 catchError(err => this.restExtractor.handleError(err))
200 )
201 }
202
dd24f1bb 203 getVideos (parameters: CommonVideoParams): Observable<ResultList<Video>> {
fd45e8f4 204 let params = new HttpParams()
dd24f1bb 205 params = this.buildCommonVideosParams({ params, ...parameters })
5c20a455 206
fd45e8f4 207 return this.authHttp
7ce44a74 208 .get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
db400f44 209 .pipe(
7ce44a74 210 switchMap(res => this.extractVideos(res)),
e4f0e92e 211 catchError(err => this.restExtractor.handleError(err))
db400f44 212 )
fd45e8f4
C
213 }
214
5beb89f2 215 buildBaseFeedUrls (params: HttpParams, base = VideoService.BASE_FEEDS_URL) {
cc1561f9
C
216 const feeds = [
217 {
39ba2e8e 218 format: FeedFormat.RSS,
2d011d94 219 label: 'media rss 2.0',
5beb89f2 220 url: base + FeedFormat.RSS.toLowerCase()
cc1561f9
C
221 },
222 {
39ba2e8e 223 format: FeedFormat.ATOM,
cc1561f9 224 label: 'atom 1.0',
5beb89f2 225 url: base + FeedFormat.ATOM.toLowerCase()
cc1561f9
C
226 },
227 {
39ba2e8e 228 format: FeedFormat.JSON,
cc1561f9 229 label: 'json 1.0',
5beb89f2 230 url: base + FeedFormat.JSON.toLowerCase()
cc1561f9
C
231 }
232 ]
233
7b87d2d5
C
234 if (params && params.keys().length !== 0) {
235 for (const feed of feeds) {
236 feed.url += '?' + params.toString()
237 }
238 }
239
cc1561f9 240 return feeds
244e76a5
RK
241 }
242
5c20a455 243 getVideoFeedUrls (sort: VideoSortField, filter?: VideoFilter, categoryOneOf?: number[]) {
7b87d2d5 244 let params = this.restService.addRestGetParams(new HttpParams(), undefined, sort)
244e76a5 245
cc1561f9
C
246 if (filter) params = params.set('filter', filter)
247
5c20a455
C
248 if (categoryOneOf) {
249 for (const c of categoryOneOf) {
250 params = params.append('categoryOneOf[]', c + '')
251 }
252 }
61b909b9 253
7b87d2d5 254 return this.buildBaseFeedUrls(params)
244e76a5
RK
255 }
256
cc1561f9 257 getAccountFeedUrls (accountId: number) {
244e76a5 258 let params = this.restService.addRestGetParams(new HttpParams())
244e76a5 259 params = params.set('accountId', accountId.toString())
cc1561f9 260
7b87d2d5 261 return this.buildBaseFeedUrls(params)
244e76a5
RK
262 }
263
170726f5
C
264 getVideoChannelFeedUrls (videoChannelId: number) {
265 let params = this.restService.addRestGetParams(new HttpParams())
266 params = params.set('videoChannelId', videoChannelId.toString())
267
268 return this.buildBaseFeedUrls(params)
269 }
270
5beb89f2 271 getVideoSubscriptionFeedUrls (accountId: number, feedToken: string) {
afff310e
RK
272 let params = this.restService.addRestGetParams(new HttpParams())
273 params = params.set('accountId', accountId.toString())
afff310e
RK
274 params = params.set('token', feedToken)
275
5beb89f2 276 return this.buildBaseFeedUrls(params, VideoService.BASE_SUBSCRIPTION_FEEDS_URL)
afff310e
RK
277 }
278
8319d6ae
RK
279 getVideoFileMetadata (metadataUrl: string) {
280 return this.authHttp
583eb04b 281 .get<VideoFileMetadata>(metadataUrl)
8319d6ae
RK
282 .pipe(
283 catchError(err => this.restExtractor.handleError(err))
284 )
285 }
286
d592e0a9 287 removeVideo (id: number) {
fd45e8f4 288 return this.authHttp
db400f44
C
289 .delete(VideoService.BASE_VIDEO_URL + id)
290 .pipe(
291 map(this.restExtractor.extractDataBool),
e4f0e92e 292 catchError(err => this.restExtractor.handleError(err))
db400f44 293 )
4fd8aa32
C
294 }
295
2de96f4d
C
296 loadCompleteDescription (descriptionPath: string) {
297 return this.authHttp
c199c427 298 .get<{ description: string }>(environment.apiUrl + descriptionPath)
db400f44 299 .pipe(
c199c427 300 map(res => res.description),
e4f0e92e 301 catchError(err => this.restExtractor.handleError(err))
db400f44 302 )
d38b8281
C
303 }
304
0a6658fd 305 setVideoLike (id: number) {
df98563e 306 return this.setVideoRate(id, 'like')
d38b8281
C
307 }
308
0a6658fd 309 setVideoDislike (id: number) {
df98563e 310 return this.setVideoRate(id, 'dislike')
d38b8281
C
311 }
312
57a49263
BB
313 unsetVideoLike (id: number) {
314 return this.setVideoRate(id, 'none')
315 }
316
5fcbd898 317 getUserVideoRating (id: number) {
334ddfa4 318 const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
d38b8281 319
5fcbd898 320 return this.authHttp.get<UserVideoRate>(url)
e4f0e92e 321 .pipe(catchError(err => this.restExtractor.handleError(err)))
d38b8281
C
322 }
323
57c36b27 324 extractVideos (result: ResultList<VideoServerModel>) {
ba430d75 325 return this.serverService.getServerLocale()
2186386c
C
326 .pipe(
327 map(translations => {
328 const videosJson = result.data
329 const totalVideos = result.total
330 const videos: Video[] = []
331
332 for (const videoJson of videosJson) {
333 videos.push(new Video(videoJson, translations))
334 }
335
93cae479 336 return { total: totalVideos, data: videos }
2186386c
C
337 })
338 )
501bc6c2 339 }
57c36b27 340
b980bcff 341 explainedPrivacyLabels (serverPrivacies: VideoConstant<VideoPrivacy>[], defaultPrivacyId = VideoPrivacy.PUBLIC) {
d91714ca
C
342 const descriptions = {
343 [VideoPrivacy.PRIVATE]: $localize`Only I can see this video`,
344 [VideoPrivacy.UNLISTED]: $localize`Only shareable via a private link`,
345 [VideoPrivacy.PUBLIC]: $localize`Anyone can see this video`,
346 [VideoPrivacy.INTERNAL]: $localize`Only users of this instance can see this video`
347 }
348
349 const videoPrivacies = serverPrivacies.map(p => {
350 return {
351 ...p,
352
353 description: descriptions[p.id]
22a73cb8 354 }
d91714ca 355 })
8cd7faaa 356
29510651 357 return {
d91714ca
C
358 videoPrivacies,
359 defaultPrivacyId: serverPrivacies.find(p => p.id === defaultPrivacyId)?.id || serverPrivacies[0].id
29510651 360 }
8cd7faaa
C
361 }
362
a3f45a2a
C
363 getHighestAvailablePrivacy (serverPrivacies: VideoConstant<VideoPrivacy>[]) {
364 const order = [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL, VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC ]
365
366 for (const privacy of order) {
367 if (serverPrivacies.find(p => p.id === privacy)) {
368 return privacy
369 }
370 }
371
372 throw new Error('No highest privacy available')
373 }
374
5c20a455
C
375 nsfwPolicyToParam (nsfwPolicy: NSFWPolicyType) {
376 return nsfwPolicy === 'do_not_list'
377 ? 'false'
378 : 'both'
379 }
380
5c6d985f 381 private setVideoRate (id: number, rateType: UserVideoRateType) {
57c36b27
C
382 const url = VideoService.BASE_VIDEO_URL + id + '/rate'
383 const body: UserVideoRateUpdate = {
384 rating: rateType
385 }
386
387 return this.authHttp
388 .put(url, body)
389 .pipe(
390 map(this.restExtractor.extractDataBool),
391 catchError(err => this.restExtractor.handleError(err))
392 )
393 }
dd24f1bb
C
394
395 private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) {
396 const { params, videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount, nsfwPolicy, isLive, nsfw } = options
397
4beda9e1 398 const pagination = this.restService.componentToRestPagination(videoPagination)
dd24f1bb
C
399 let newParams = this.restService.addRestGetParams(params, pagination, sort)
400
401 if (filter) newParams = newParams.set('filter', filter)
402 if (skipCount) newParams = newParams.set('skipCount', skipCount + '')
403
404 if (isLive) newParams = newParams.set('isLive', isLive)
405 if (nsfw) newParams = newParams.set('nsfw', nsfw)
406 if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
407 if (languageOneOf) newParams = this.restService.addArrayParams(newParams, 'languageOneOf', languageOneOf)
408 if (categoryOneOf) newParams = this.restService.addArrayParams(newParams, 'categoryOneOf', categoryOneOf)
409
410 return newParams
411 }
dc8bc31b 412}