diff options
Diffstat (limited to 'client')
26 files changed, 274 insertions, 99 deletions
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 9ae6f9f12..b3818c8de 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts | |||
@@ -20,12 +20,12 @@ import { | |||
20 | } from '@app/core' | 20 | } from '@app/core' |
21 | import { HooksService } from '@app/core/plugins/hooks.service' | 21 | import { HooksService } from '@app/core/plugins/hooks.service' |
22 | import { isXPercentInViewport, scrollToTop } from '@app/helpers' | 22 | import { isXPercentInViewport, scrollToTop } from '@app/helpers' |
23 | import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main' | 23 | import { Video, VideoCaptionService, VideoDetails, VideoFileTokenService, VideoService } from '@app/shared/shared-main' |
24 | import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' | 24 | import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' |
25 | import { LiveVideoService } from '@app/shared/shared-video-live' | 25 | import { LiveVideoService } from '@app/shared/shared-video-live' |
26 | import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' | 26 | import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' |
27 | import { logger } from '@root-helpers/logger' | 27 | import { logger } from '@root-helpers/logger' |
28 | import { isP2PEnabled } from '@root-helpers/video' | 28 | import { isP2PEnabled, videoRequiresAuth } from '@root-helpers/video' |
29 | import { timeToInt } from '@shared/core-utils' | 29 | import { timeToInt } from '@shared/core-utils' |
30 | import { | 30 | import { |
31 | HTMLServerConfig, | 31 | HTMLServerConfig, |
@@ -78,6 +78,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
78 | private nextVideoUUID = '' | 78 | private nextVideoUUID = '' |
79 | private nextVideoTitle = '' | 79 | private nextVideoTitle = '' |
80 | 80 | ||
81 | private videoFileToken: string | ||
82 | |||
81 | private currentTime: number | 83 | private currentTime: number |
82 | 84 | ||
83 | private paramsSub: Subscription | 85 | private paramsSub: Subscription |
@@ -110,6 +112,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
110 | private pluginService: PluginService, | 112 | private pluginService: PluginService, |
111 | private peertubeSocket: PeerTubeSocket, | 113 | private peertubeSocket: PeerTubeSocket, |
112 | private screenService: ScreenService, | 114 | private screenService: ScreenService, |
115 | private videoFileTokenService: VideoFileTokenService, | ||
113 | private location: PlatformLocation, | 116 | private location: PlatformLocation, |
114 | @Inject(LOCALE_ID) private localeId: string | 117 | @Inject(LOCALE_ID) private localeId: string |
115 | ) { } | 118 | ) { } |
@@ -252,12 +255,19 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
252 | 'filter:api.video-watch.video.get.result' | 255 | 'filter:api.video-watch.video.get.result' |
253 | ) | 256 | ) |
254 | 257 | ||
255 | const videoAndLiveObs: Observable<{ video: VideoDetails, live?: LiveVideo }> = videoObs.pipe( | 258 | const videoAndLiveObs: Observable<{ video: VideoDetails, live?: LiveVideo, videoFileToken?: string }> = videoObs.pipe( |
256 | switchMap(video => { | 259 | switchMap(video => { |
257 | if (!video.isLive) return of({ video }) | 260 | if (!video.isLive) return of({ video, live: undefined }) |
258 | 261 | ||
259 | return this.liveVideoService.getVideoLive(video.uuid) | 262 | return this.liveVideoService.getVideoLive(video.uuid) |
260 | .pipe(map(live => ({ live, video }))) | 263 | .pipe(map(live => ({ live, video }))) |
264 | }), | ||
265 | |||
266 | switchMap(({ video, live }) => { | ||
267 | if (!videoRequiresAuth(video)) return of({ video, live, videoFileToken: undefined }) | ||
268 | |||
269 | return this.videoFileTokenService.getVideoFileToken(video.uuid) | ||
270 | .pipe(map(({ token }) => ({ video, live, videoFileToken: token }))) | ||
261 | }) | 271 | }) |
262 | ) | 272 | ) |
263 | 273 | ||
@@ -266,7 +276,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
266 | this.videoCaptionService.listCaptions(videoId), | 276 | this.videoCaptionService.listCaptions(videoId), |
267 | this.userService.getAnonymousOrLoggedUser() | 277 | this.userService.getAnonymousOrLoggedUser() |
268 | ]).subscribe({ | 278 | ]).subscribe({ |
269 | next: ([ { video, live }, captionsResult, loggedInOrAnonymousUser ]) => { | 279 | next: ([ { video, live, videoFileToken }, captionsResult, loggedInOrAnonymousUser ]) => { |
270 | const queryParams = this.route.snapshot.queryParams | 280 | const queryParams = this.route.snapshot.queryParams |
271 | 281 | ||
272 | const urlOptions = { | 282 | const urlOptions = { |
@@ -283,7 +293,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
283 | peertubeLink: false | 293 | peertubeLink: false |
284 | } | 294 | } |
285 | 295 | ||
286 | this.onVideoFetched({ video, live, videoCaptions: captionsResult.data, loggedInOrAnonymousUser, urlOptions }) | 296 | this.onVideoFetched({ video, live, videoCaptions: captionsResult.data, videoFileToken, loggedInOrAnonymousUser, urlOptions }) |
287 | .catch(err => this.handleGlobalError(err)) | 297 | .catch(err => this.handleGlobalError(err)) |
288 | }, | 298 | }, |
289 | 299 | ||
@@ -356,16 +366,19 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
356 | video: VideoDetails | 366 | video: VideoDetails |
357 | live: LiveVideo | 367 | live: LiveVideo |
358 | videoCaptions: VideoCaption[] | 368 | videoCaptions: VideoCaption[] |
369 | videoFileToken: string | ||
370 | |||
359 | urlOptions: URLOptions | 371 | urlOptions: URLOptions |
360 | loggedInOrAnonymousUser: User | 372 | loggedInOrAnonymousUser: User |
361 | }) { | 373 | }) { |
362 | const { video, live, videoCaptions, urlOptions, loggedInOrAnonymousUser } = options | 374 | const { video, live, videoCaptions, urlOptions, videoFileToken, loggedInOrAnonymousUser } = options |
363 | 375 | ||
364 | this.subscribeToLiveEventsIfNeeded(this.video, video) | 376 | this.subscribeToLiveEventsIfNeeded(this.video, video) |
365 | 377 | ||
366 | this.video = video | 378 | this.video = video |
367 | this.videoCaptions = videoCaptions | 379 | this.videoCaptions = videoCaptions |
368 | this.liveVideo = live | 380 | this.liveVideo = live |
381 | this.videoFileToken = videoFileToken | ||
369 | 382 | ||
370 | // Re init attributes | 383 | // Re init attributes |
371 | this.playerPlaceholderImgSrc = undefined | 384 | this.playerPlaceholderImgSrc = undefined |
@@ -414,6 +427,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
414 | video: this.video, | 427 | video: this.video, |
415 | videoCaptions: this.videoCaptions, | 428 | videoCaptions: this.videoCaptions, |
416 | liveVideo: this.liveVideo, | 429 | liveVideo: this.liveVideo, |
430 | videoFileToken: this.videoFileToken, | ||
417 | urlOptions, | 431 | urlOptions, |
418 | loggedInOrAnonymousUser, | 432 | loggedInOrAnonymousUser, |
419 | user: this.user | 433 | user: this.user |
@@ -561,11 +575,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
561 | video: VideoDetails | 575 | video: VideoDetails |
562 | liveVideo: LiveVideo | 576 | liveVideo: LiveVideo |
563 | videoCaptions: VideoCaption[] | 577 | videoCaptions: VideoCaption[] |
578 | |||
579 | videoFileToken: string | ||
580 | |||
564 | urlOptions: CustomizationOptions & { playerMode: PlayerMode } | 581 | urlOptions: CustomizationOptions & { playerMode: PlayerMode } |
582 | |||
565 | loggedInOrAnonymousUser: User | 583 | loggedInOrAnonymousUser: User |
566 | user?: AuthUser // Keep for plugins | 584 | user?: AuthUser // Keep for plugins |
567 | }) { | 585 | }) { |
568 | const { video, liveVideo, videoCaptions, urlOptions, loggedInOrAnonymousUser } = params | 586 | const { video, liveVideo, videoCaptions, videoFileToken, urlOptions, loggedInOrAnonymousUser } = params |
569 | 587 | ||
570 | const getStartTime = () => { | 588 | const getStartTime = () => { |
571 | const byUrl = urlOptions.startTime !== undefined | 589 | const byUrl = urlOptions.startTime !== undefined |
@@ -623,13 +641,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
623 | theaterButton: true, | 641 | theaterButton: true, |
624 | captions: videoCaptions.length !== 0, | 642 | captions: videoCaptions.length !== 0, |
625 | 643 | ||
626 | videoViewUrl: video.privacy.id !== VideoPrivacy.PRIVATE | ||
627 | ? this.videoService.getVideoViewUrl(video.uuid) | ||
628 | : null, | ||
629 | authorizationHeader: this.authService.getRequestHeaderValue(), | ||
630 | |||
631 | metricsUrl: environment.apiUrl + '/api/v1/metrics/playback', | ||
632 | |||
633 | embedUrl: video.embedUrl, | 644 | embedUrl: video.embedUrl, |
634 | embedTitle: video.name, | 645 | embedTitle: video.name, |
635 | instanceName: this.serverConfig.instance.name, | 646 | instanceName: this.serverConfig.instance.name, |
@@ -639,7 +650,17 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
639 | 650 | ||
640 | language: this.localeId, | 651 | language: this.localeId, |
641 | 652 | ||
642 | serverUrl: environment.apiUrl, | 653 | metricsUrl: environment.apiUrl + '/api/v1/metrics/playback', |
654 | |||
655 | videoViewUrl: video.privacy.id !== VideoPrivacy.PRIVATE | ||
656 | ? this.videoService.getVideoViewUrl(video.uuid) | ||
657 | : null, | ||
658 | authorizationHeader: () => this.authService.getRequestHeaderValue(), | ||
659 | |||
660 | serverUrl: environment.originServerUrl, | ||
661 | |||
662 | videoFileToken: () => videoFileToken, | ||
663 | requiresAuth: videoRequiresAuth(video), | ||
643 | 664 | ||
644 | videoCaptions: playerCaptions, | 665 | videoCaptions: playerCaptions, |
645 | 666 | ||
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index cd9665e37..a12325421 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Observable, of } from 'rxjs' | 1 | import { Observable, of } from 'rxjs' |
2 | import { map } from 'rxjs/operators' | 2 | import { map } from 'rxjs/operators' |
3 | import { User } from '@app/core/users/user.model' | 3 | import { User } from '@app/core/users/user.model' |
4 | import { UserTokens } from '@root-helpers/users' | 4 | import { OAuthUserTokens } from '@root-helpers/users' |
5 | import { hasUserRight } from '@shared/core-utils/users' | 5 | import { hasUserRight } from '@shared/core-utils/users' |
6 | import { | 6 | import { |
7 | MyUser as ServerMyUserModel, | 7 | MyUser as ServerMyUserModel, |
@@ -13,33 +13,33 @@ import { | |||
13 | } from '@shared/models' | 13 | } from '@shared/models' |
14 | 14 | ||
15 | export class AuthUser extends User implements ServerMyUserModel { | 15 | export class AuthUser extends User implements ServerMyUserModel { |
16 | tokens: UserTokens | 16 | oauthTokens: OAuthUserTokens |
17 | specialPlaylists: MyUserSpecialPlaylist[] | 17 | specialPlaylists: MyUserSpecialPlaylist[] |
18 | 18 | ||
19 | canSeeVideosLink = true | 19 | canSeeVideosLink = true |
20 | 20 | ||
21 | constructor (userHash: Partial<ServerMyUserModel>, hashTokens: Partial<UserTokens>) { | 21 | constructor (userHash: Partial<ServerMyUserModel>, hashTokens: Partial<OAuthUserTokens>) { |
22 | super(userHash) | 22 | super(userHash) |
23 | 23 | ||
24 | this.tokens = new UserTokens(hashTokens) | 24 | this.oauthTokens = new OAuthUserTokens(hashTokens) |
25 | this.specialPlaylists = userHash.specialPlaylists | 25 | this.specialPlaylists = userHash.specialPlaylists |
26 | } | 26 | } |
27 | 27 | ||
28 | getAccessToken () { | 28 | getAccessToken () { |
29 | return this.tokens.accessToken | 29 | return this.oauthTokens.accessToken |
30 | } | 30 | } |
31 | 31 | ||
32 | getRefreshToken () { | 32 | getRefreshToken () { |
33 | return this.tokens.refreshToken | 33 | return this.oauthTokens.refreshToken |
34 | } | 34 | } |
35 | 35 | ||
36 | getTokenType () { | 36 | getTokenType () { |
37 | return this.tokens.tokenType | 37 | return this.oauthTokens.tokenType |
38 | } | 38 | } |
39 | 39 | ||
40 | refreshTokens (accessToken: string, refreshToken: string) { | 40 | refreshTokens (accessToken: string, refreshToken: string) { |
41 | this.tokens.accessToken = accessToken | 41 | this.oauthTokens.accessToken = accessToken |
42 | this.tokens.refreshToken = refreshToken | 42 | this.oauthTokens.refreshToken = refreshToken |
43 | } | 43 | } |
44 | 44 | ||
45 | hasRight (right: UserRight) { | 45 | hasRight (right: UserRight) { |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 7f4fae4aa..4de28e51e 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -5,7 +5,7 @@ import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular | |||
5 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
6 | import { Router } from '@angular/router' | 6 | import { Router } from '@angular/router' |
7 | import { Notifier } from '@app/core/notification/notifier.service' | 7 | import { Notifier } from '@app/core/notification/notifier.service' |
8 | import { logger, objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index' | 8 | import { logger, OAuthUserTokens, objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index' |
9 | import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' | 9 | import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' |
10 | import { environment } from '../../../environments/environment' | 10 | import { environment } from '../../../environments/environment' |
11 | import { RestExtractor } from '../rest/rest-extractor.service' | 11 | import { RestExtractor } from '../rest/rest-extractor.service' |
@@ -74,7 +74,7 @@ export class AuthService { | |||
74 | ] | 74 | ] |
75 | } | 75 | } |
76 | 76 | ||
77 | buildAuthUser (userInfo: Partial<User>, tokens: UserTokens) { | 77 | buildAuthUser (userInfo: Partial<User>, tokens: OAuthUserTokens) { |
78 | this.user = new AuthUser(userInfo, tokens) | 78 | this.user = new AuthUser(userInfo, tokens) |
79 | } | 79 | } |
80 | 80 | ||
diff --git a/client/src/app/core/users/user-local-storage.service.ts b/client/src/app/core/users/user-local-storage.service.ts index fff649eef..f1588bdd2 100644 --- a/client/src/app/core/users/user-local-storage.service.ts +++ b/client/src/app/core/users/user-local-storage.service.ts | |||
@@ -4,7 +4,7 @@ import { Injectable } from '@angular/core' | |||
4 | import { AuthService, AuthStatus } from '@app/core/auth' | 4 | import { AuthService, AuthStatus } from '@app/core/auth' |
5 | import { getBoolOrDefault } from '@root-helpers/local-storage-utils' | 5 | import { getBoolOrDefault } from '@root-helpers/local-storage-utils' |
6 | import { logger } from '@root-helpers/logger' | 6 | import { logger } from '@root-helpers/logger' |
7 | import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users' | 7 | import { UserLocalStorageKeys, OAuthUserTokens } from '@root-helpers/users' |
8 | import { UserRole, UserUpdateMe } from '@shared/models' | 8 | import { UserRole, UserUpdateMe } from '@shared/models' |
9 | import { NSFWPolicyType } from '@shared/models/videos' | 9 | import { NSFWPolicyType } from '@shared/models/videos' |
10 | import { ServerService } from '../server' | 10 | import { ServerService } from '../server' |
@@ -24,7 +24,7 @@ export class UserLocalStorageService { | |||
24 | 24 | ||
25 | this.setLoggedInUser(user) | 25 | this.setLoggedInUser(user) |
26 | this.setUserInfo(user) | 26 | this.setUserInfo(user) |
27 | this.setTokens(user.tokens) | 27 | this.setTokens(user.oauthTokens) |
28 | } | 28 | } |
29 | }) | 29 | }) |
30 | 30 | ||
@@ -43,7 +43,7 @@ export class UserLocalStorageService { | |||
43 | next: () => { | 43 | next: () => { |
44 | const user = this.authService.getUser() | 44 | const user = this.authService.getUser() |
45 | 45 | ||
46 | this.setTokens(user.tokens) | 46 | this.setTokens(user.oauthTokens) |
47 | } | 47 | } |
48 | }) | 48 | }) |
49 | } | 49 | } |
@@ -174,14 +174,14 @@ export class UserLocalStorageService { | |||
174 | // --------------------------------------------------------------------------- | 174 | // --------------------------------------------------------------------------- |
175 | 175 | ||
176 | getTokens () { | 176 | getTokens () { |
177 | return UserTokens.getUserTokens(this.localStorageService) | 177 | return OAuthUserTokens.getUserTokens(this.localStorageService) |
178 | } | 178 | } |
179 | 179 | ||
180 | setTokens (tokens: UserTokens) { | 180 | setTokens (tokens: OAuthUserTokens) { |
181 | UserTokens.saveToLocalStorage(this.localStorageService, tokens) | 181 | OAuthUserTokens.saveToLocalStorage(this.localStorageService, tokens) |
182 | } | 182 | } |
183 | 183 | ||
184 | flushTokens () { | 184 | flushTokens () { |
185 | UserTokens.flushLocalStorage(this.localStorageService) | 185 | OAuthUserTokens.flushLocalStorage(this.localStorageService) |
186 | } | 186 | } |
187 | } | 187 | } |
diff --git a/client/src/app/helpers/utils/url.ts b/client/src/app/helpers/utils/url.ts index 08c27e3c1..9e7dc3e6f 100644 --- a/client/src/app/helpers/utils/url.ts +++ b/client/src/app/helpers/utils/url.ts | |||
@@ -54,8 +54,9 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) { | |||
54 | } | 54 | } |
55 | 55 | ||
56 | export { | 56 | export { |
57 | objectToFormData, | ||
58 | getAbsoluteAPIUrl, | 57 | getAbsoluteAPIUrl, |
59 | getAPIHost, | 58 | getAPIHost, |
60 | getAbsoluteEmbedUrl | 59 | getAbsoluteEmbedUrl, |
60 | |||
61 | objectToFormData | ||
61 | } | 62 | } |
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index 04b223cc5..c1523bc50 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts | |||
@@ -44,7 +44,15 @@ import { | |||
44 | import { PluginPlaceholderComponent, PluginSelectorDirective } from './plugins' | 44 | import { PluginPlaceholderComponent, PluginSelectorDirective } from './plugins' |
45 | import { ActorRedirectGuard } from './router' | 45 | import { ActorRedirectGuard } from './router' |
46 | import { UserHistoryService, UserNotificationsComponent, UserNotificationService, UserQuotaComponent } from './users' | 46 | import { UserHistoryService, UserNotificationsComponent, UserNotificationService, UserQuotaComponent } from './users' |
47 | import { EmbedComponent, RedundancyService, VideoImportService, VideoOwnershipService, VideoResolver, VideoService } from './video' | 47 | import { |
48 | EmbedComponent, | ||
49 | RedundancyService, | ||
50 | VideoFileTokenService, | ||
51 | VideoImportService, | ||
52 | VideoOwnershipService, | ||
53 | VideoResolver, | ||
54 | VideoService | ||
55 | } from './video' | ||
48 | import { VideoCaptionService } from './video-caption' | 56 | import { VideoCaptionService } from './video-caption' |
49 | import { VideoChannelService } from './video-channel' | 57 | import { VideoChannelService } from './video-channel' |
50 | 58 | ||
@@ -185,6 +193,7 @@ import { VideoChannelService } from './video-channel' | |||
185 | VideoImportService, | 193 | VideoImportService, |
186 | VideoOwnershipService, | 194 | VideoOwnershipService, |
187 | VideoService, | 195 | VideoService, |
196 | VideoFileTokenService, | ||
188 | VideoResolver, | 197 | VideoResolver, |
189 | 198 | ||
190 | VideoCaptionService, | 199 | VideoCaptionService, |
diff --git a/client/src/app/shared/shared-main/video/index.ts b/client/src/app/shared/shared-main/video/index.ts index 361601456..a2e47883e 100644 --- a/client/src/app/shared/shared-main/video/index.ts +++ b/client/src/app/shared/shared-main/video/index.ts | |||
@@ -2,6 +2,7 @@ export * from './embed.component' | |||
2 | export * from './redundancy.service' | 2 | export * from './redundancy.service' |
3 | export * from './video-details.model' | 3 | export * from './video-details.model' |
4 | export * from './video-edit.model' | 4 | export * from './video-edit.model' |
5 | export * from './video-file-token.service' | ||
5 | export * from './video-import.service' | 6 | export * from './video-import.service' |
6 | export * from './video-ownership.service' | 7 | export * from './video-ownership.service' |
7 | export * from './video.model' | 8 | export * from './video.model' |
diff --git a/client/src/app/shared/shared-main/video/video-file-token.service.ts b/client/src/app/shared/shared-main/video/video-file-token.service.ts new file mode 100644 index 000000000..791607249 --- /dev/null +++ b/client/src/app/shared/shared-main/video/video-file-token.service.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import { catchError, map, of, tap } from 'rxjs' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { RestExtractor } from '@app/core' | ||
5 | import { VideoToken } from '@shared/models' | ||
6 | import { VideoService } from './video.service' | ||
7 | |||
8 | @Injectable() | ||
9 | export class VideoFileTokenService { | ||
10 | |||
11 | private readonly store = new Map<string, { token: string, expires: Date }>() | ||
12 | |||
13 | constructor ( | ||
14 | private authHttp: HttpClient, | ||
15 | private restExtractor: RestExtractor | ||
16 | ) {} | ||
17 | |||
18 | getVideoFileToken (videoUUID: string) { | ||
19 | const existing = this.store.get(videoUUID) | ||
20 | if (existing) return of(existing) | ||
21 | |||
22 | return this.createVideoFileToken(videoUUID) | ||
23 | .pipe(tap(result => this.store.set(videoUUID, { token: result.token, expires: new Date(result.expires) }))) | ||
24 | } | ||
25 | |||
26 | private createVideoFileToken (videoUUID: string) { | ||
27 | return this.authHttp.post<VideoToken>(`${VideoService.BASE_VIDEO_URL}/${videoUUID}/token`, {}) | ||
28 | .pipe( | ||
29 | map(({ files }) => files), | ||
30 | catchError(err => this.restExtractor.handleError(err)) | ||
31 | ) | ||
32 | } | ||
33 | } | ||
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.html b/client/src/app/shared/shared-video-miniature/video-download.component.html index 1c7458b4b..1f622933d 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.html +++ b/client/src/app/shared/shared-video-miniature/video-download.component.html | |||
@@ -48,10 +48,7 @@ | |||
48 | 48 | ||
49 | <ng-template ngbNavContent> | 49 | <ng-template ngbNavContent> |
50 | <div class="nav-content"> | 50 | <div class="nav-content"> |
51 | <my-input-text | 51 | <my-input-text [show]="true" [readonly]="true" [withCopy]="true" [withToggle]="false" [value]="getLink()"></my-input-text> |
52 | *ngIf="!isConfidentialVideo()" | ||
53 | [show]="true" [readonly]="true" [withCopy]="true" [withToggle]="false" [value]="getLink()" | ||
54 | ></my-input-text> | ||
55 | </div> | 52 | </div> |
56 | </ng-template> | 53 | </ng-template> |
57 | </ng-container> | 54 | </ng-container> |
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts index 47482caaa..667cb107f 100644 --- a/client/src/app/shared/shared-video-miniature/video-download.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts | |||
@@ -2,11 +2,12 @@ import { mapValues, pick } from 'lodash-es' | |||
2 | import { firstValueFrom } from 'rxjs' | 2 | import { firstValueFrom } from 'rxjs' |
3 | import { tap } from 'rxjs/operators' | 3 | import { tap } from 'rxjs/operators' |
4 | import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core' | 4 | import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core' |
5 | import { AuthService, HooksService, Notifier } from '@app/core' | 5 | import { HooksService } from '@app/core' |
6 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' |
7 | import { logger } from '@root-helpers/logger' | 7 | import { logger } from '@root-helpers/logger' |
8 | import { videoRequiresAuth } from '@root-helpers/video' | ||
8 | import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models' | 9 | import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models' |
9 | import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main' | 10 | import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoFileTokenService, VideoService } from '../shared-main' |
10 | 11 | ||
11 | type DownloadType = 'video' | 'subtitles' | 12 | type DownloadType = 'video' | 'subtitles' |
12 | type FileMetadata = { [key: string]: { label: string, value: string }} | 13 | type FileMetadata = { [key: string]: { label: string, value: string }} |
@@ -32,6 +33,8 @@ export class VideoDownloadComponent { | |||
32 | 33 | ||
33 | type: DownloadType = 'video' | 34 | type: DownloadType = 'video' |
34 | 35 | ||
36 | videoFileToken: string | ||
37 | |||
35 | private activeModal: NgbModalRef | 38 | private activeModal: NgbModalRef |
36 | 39 | ||
37 | private bytesPipe: BytesPipe | 40 | private bytesPipe: BytesPipe |
@@ -42,10 +45,9 @@ export class VideoDownloadComponent { | |||
42 | 45 | ||
43 | constructor ( | 46 | constructor ( |
44 | @Inject(LOCALE_ID) private localeId: string, | 47 | @Inject(LOCALE_ID) private localeId: string, |
45 | private notifier: Notifier, | ||
46 | private modalService: NgbModal, | 48 | private modalService: NgbModal, |
47 | private videoService: VideoService, | 49 | private videoService: VideoService, |
48 | private auth: AuthService, | 50 | private videoFileTokenService: VideoFileTokenService, |
49 | private hooks: HooksService | 51 | private hooks: HooksService |
50 | ) { | 52 | ) { |
51 | this.bytesPipe = new BytesPipe() | 53 | this.bytesPipe = new BytesPipe() |
@@ -71,6 +73,8 @@ export class VideoDownloadComponent { | |||
71 | } | 73 | } |
72 | 74 | ||
73 | show (video: VideoDetails, videoCaptions?: VideoCaption[]) { | 75 | show (video: VideoDetails, videoCaptions?: VideoCaption[]) { |
76 | this.videoFileToken = undefined | ||
77 | |||
74 | this.video = video | 78 | this.video = video |
75 | this.videoCaptions = videoCaptions | 79 | this.videoCaptions = videoCaptions |
76 | 80 | ||
@@ -84,6 +88,11 @@ export class VideoDownloadComponent { | |||
84 | this.subtitleLanguageId = this.videoCaptions[0].language.id | 88 | this.subtitleLanguageId = this.videoCaptions[0].language.id |
85 | } | 89 | } |
86 | 90 | ||
91 | if (videoRequiresAuth(this.video)) { | ||
92 | this.videoFileTokenService.getVideoFileToken(this.video.uuid) | ||
93 | .subscribe(({ token }) => this.videoFileToken = token) | ||
94 | } | ||
95 | |||
87 | this.activeModal.shown.subscribe(() => { | 96 | this.activeModal.shown.subscribe(() => { |
88 | this.hooks.runAction('action:modal.video-download.shown', 'common') | 97 | this.hooks.runAction('action:modal.video-download.shown', 'common') |
89 | }) | 98 | }) |
@@ -155,7 +164,7 @@ export class VideoDownloadComponent { | |||
155 | if (!file) return '' | 164 | if (!file) return '' |
156 | 165 | ||
157 | const suffix = this.isConfidentialVideo() | 166 | const suffix = this.isConfidentialVideo() |
158 | ? '?access_token=' + this.auth.getAccessToken() | 167 | ? '?videoFileToken=' + this.videoFileToken |
159 | : '' | 168 | : '' |
160 | 169 | ||
161 | switch (this.downloadType) { | 170 | switch (this.downloadType) { |
diff --git a/client/src/assets/player/shared/common/utils.ts b/client/src/assets/player/shared/common/utils.ts index a010d9184..609240626 100644 --- a/client/src/assets/player/shared/common/utils.ts +++ b/client/src/assets/player/shared/common/utils.ts | |||
@@ -52,6 +52,10 @@ function getRtcConfig () { | |||
52 | } | 52 | } |
53 | } | 53 | } |
54 | 54 | ||
55 | function isSameOrigin (current: string, target: string) { | ||
56 | return new URL(current).origin === new URL(target).origin | ||
57 | } | ||
58 | |||
55 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
56 | 60 | ||
57 | export { | 61 | export { |
@@ -60,5 +64,7 @@ export { | |||
60 | 64 | ||
61 | videoFileMaxByResolution, | 65 | videoFileMaxByResolution, |
62 | videoFileMinByResolution, | 66 | videoFileMinByResolution, |
63 | bytes | 67 | bytes, |
68 | |||
69 | isSameOrigin | ||
64 | } | 70 | } |
diff --git a/client/src/assets/player/shared/manager-options/hls-options-builder.ts b/client/src/assets/player/shared/manager-options/hls-options-builder.ts index 361c76f4b..933c0d595 100644 --- a/client/src/assets/player/shared/manager-options/hls-options-builder.ts +++ b/client/src/assets/player/shared/manager-options/hls-options-builder.ts | |||
@@ -5,7 +5,7 @@ import { LiveVideoLatencyMode } from '@shared/models' | |||
5 | import { getAverageBandwidthInStore } from '../../peertube-player-local-storage' | 5 | import { getAverageBandwidthInStore } from '../../peertube-player-local-storage' |
6 | import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types' | 6 | import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types' |
7 | import { PeertubePlayerManagerOptions } from '../../types/manager-options' | 7 | import { PeertubePlayerManagerOptions } from '../../types/manager-options' |
8 | import { getRtcConfig } from '../common' | 8 | import { getRtcConfig, isSameOrigin } from '../common' |
9 | import { RedundancyUrlManager } from '../p2p-media-loader/redundancy-url-manager' | 9 | import { RedundancyUrlManager } from '../p2p-media-loader/redundancy-url-manager' |
10 | import { segmentUrlBuilderFactory } from '../p2p-media-loader/segment-url-builder' | 10 | import { segmentUrlBuilderFactory } from '../p2p-media-loader/segment-url-builder' |
11 | import { segmentValidatorFactory } from '../p2p-media-loader/segment-validator' | 11 | import { segmentValidatorFactory } from '../p2p-media-loader/segment-validator' |
@@ -84,7 +84,21 @@ export class HLSOptionsBuilder { | |||
84 | simultaneousHttpDownloads: 1, | 84 | simultaneousHttpDownloads: 1, |
85 | httpFailedSegmentTimeout: 1000, | 85 | httpFailedSegmentTimeout: 1000, |
86 | 86 | ||
87 | segmentValidator: segmentValidatorFactory(this.options.p2pMediaLoader.segmentsSha256Url, this.options.common.isLive), | 87 | xhrSetup: (xhr, url) => { |
88 | if (!this.options.common.requiresAuth) return | ||
89 | if (!isSameOrigin(this.options.common.serverUrl, url)) return | ||
90 | |||
91 | xhr.setRequestHeader('Authorization', this.options.common.authorizationHeader()) | ||
92 | }, | ||
93 | |||
94 | segmentValidator: segmentValidatorFactory({ | ||
95 | segmentsSha256Url: this.options.p2pMediaLoader.segmentsSha256Url, | ||
96 | isLive: this.options.common.isLive, | ||
97 | authorizationHeader: this.options.common.authorizationHeader, | ||
98 | requiresAuth: this.options.common.requiresAuth, | ||
99 | serverUrl: this.options.common.serverUrl | ||
100 | }), | ||
101 | |||
88 | segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager), | 102 | segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager), |
89 | 103 | ||
90 | useP2P: this.options.common.p2pEnabled, | 104 | useP2P: this.options.common.p2pEnabled, |
diff --git a/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts b/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts index 257cf1e05..b5bdcd4e6 100644 --- a/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts +++ b/client/src/assets/player/shared/manager-options/webtorrent-options-builder.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { PeertubePlayerManagerOptions } from '../../types' | 1 | import { addQueryParams } from '../../../../../../shared/core-utils' |
2 | import { PeertubePlayerManagerOptions, WebtorrentPluginOptions } from '../../types' | ||
2 | 3 | ||
3 | export class WebTorrentOptionsBuilder { | 4 | export class WebTorrentOptionsBuilder { |
4 | 5 | ||
@@ -16,13 +17,23 @@ export class WebTorrentOptionsBuilder { | |||
16 | 17 | ||
17 | const autoplay = this.autoPlayValue === 'play' | 18 | const autoplay = this.autoPlayValue === 'play' |
18 | 19 | ||
19 | const webtorrent = { | 20 | const webtorrent: WebtorrentPluginOptions = { |
20 | autoplay, | 21 | autoplay, |
21 | 22 | ||
22 | playerRefusedP2P: commonOptions.p2pEnabled === false, | 23 | playerRefusedP2P: commonOptions.p2pEnabled === false, |
23 | videoDuration: commonOptions.videoDuration, | 24 | videoDuration: commonOptions.videoDuration, |
24 | playerElement: commonOptions.playerElement, | 25 | playerElement: commonOptions.playerElement, |
25 | 26 | ||
27 | videoFileToken: commonOptions.videoFileToken, | ||
28 | |||
29 | requiresAuth: commonOptions.requiresAuth, | ||
30 | |||
31 | buildWebSeedUrls: file => { | ||
32 | if (!commonOptions.requiresAuth) return [] | ||
33 | |||
34 | return [ addQueryParams(file.fileUrl, { videoFileToken: commonOptions.videoFileToken() }) ] | ||
35 | }, | ||
36 | |||
26 | videoFiles: webtorrentOptions.videoFiles.length !== 0 | 37 | videoFiles: webtorrentOptions.videoFiles.length !== 0 |
27 | ? webtorrentOptions.videoFiles | 38 | ? webtorrentOptions.videoFiles |
28 | // The WebTorrent plugin won't be able to play these files, but it will fallback to HTTP mode | 39 | // The WebTorrent plugin won't be able to play these files, but it will fallback to HTTP mode |
diff --git a/client/src/assets/player/shared/p2p-media-loader/segment-validator.ts b/client/src/assets/player/shared/p2p-media-loader/segment-validator.ts index 18cb6750f..a7ee91950 100644 --- a/client/src/assets/player/shared/p2p-media-loader/segment-validator.ts +++ b/client/src/assets/player/shared/p2p-media-loader/segment-validator.ts | |||
@@ -2,13 +2,22 @@ import { basename } from 'path' | |||
2 | import { Segment } from '@peertube/p2p-media-loader-core' | 2 | import { Segment } from '@peertube/p2p-media-loader-core' |
3 | import { logger } from '@root-helpers/logger' | 3 | import { logger } from '@root-helpers/logger' |
4 | import { wait } from '@root-helpers/utils' | 4 | import { wait } from '@root-helpers/utils' |
5 | import { isSameOrigin } from '../common' | ||
5 | 6 | ||
6 | type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } } | 7 | type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } } |
7 | 8 | ||
8 | const maxRetries = 3 | 9 | const maxRetries = 3 |
9 | 10 | ||
10 | function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) { | 11 | function segmentValidatorFactory (options: { |
11 | let segmentsJSON = fetchSha256Segments(segmentsSha256Url) | 12 | serverUrl: string |
13 | segmentsSha256Url: string | ||
14 | isLive: boolean | ||
15 | authorizationHeader: () => string | ||
16 | requiresAuth: boolean | ||
17 | }) { | ||
18 | const { serverUrl, segmentsSha256Url, isLive, authorizationHeader, requiresAuth } = options | ||
19 | |||
20 | let segmentsJSON = fetchSha256Segments({ serverUrl, segmentsSha256Url, authorizationHeader, requiresAuth }) | ||
12 | const regex = /bytes=(\d+)-(\d+)/ | 21 | const regex = /bytes=(\d+)-(\d+)/ |
13 | 22 | ||
14 | return async function segmentValidator (segment: Segment, _method: string, _peerId: string, retry = 1) { | 23 | return async function segmentValidator (segment: Segment, _method: string, _peerId: string, retry = 1) { |
@@ -28,7 +37,7 @@ function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) { | |||
28 | 37 | ||
29 | await wait(1000) | 38 | await wait(1000) |
30 | 39 | ||
31 | segmentsJSON = fetchSha256Segments(segmentsSha256Url) | 40 | segmentsJSON = fetchSha256Segments({ serverUrl, segmentsSha256Url, authorizationHeader, requiresAuth }) |
32 | await segmentValidator(segment, _method, _peerId, retry + 1) | 41 | await segmentValidator(segment, _method, _peerId, retry + 1) |
33 | 42 | ||
34 | return | 43 | return |
@@ -68,8 +77,19 @@ export { | |||
68 | 77 | ||
69 | // --------------------------------------------------------------------------- | 78 | // --------------------------------------------------------------------------- |
70 | 79 | ||
71 | function fetchSha256Segments (url: string) { | 80 | function fetchSha256Segments (options: { |
72 | return fetch(url) | 81 | serverUrl: string |
82 | segmentsSha256Url: string | ||
83 | authorizationHeader: () => string | ||
84 | requiresAuth: boolean | ||
85 | }) { | ||
86 | const { serverUrl, segmentsSha256Url, requiresAuth, authorizationHeader } = options | ||
87 | |||
88 | const headers = requiresAuth && isSameOrigin(serverUrl, segmentsSha256Url) | ||
89 | ? { Authorization: authorizationHeader() } | ||
90 | : {} | ||
91 | |||
92 | return fetch(segmentsSha256Url, { headers }) | ||
73 | .then(res => res.json() as Promise<SegmentsJSON>) | 93 | .then(res => res.json() as Promise<SegmentsJSON>) |
74 | .catch(err => { | 94 | .catch(err => { |
75 | logger.error('Cannot get sha256 segments', err) | 95 | logger.error('Cannot get sha256 segments', err) |
diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts index a5d712d70..4bd038bb1 100644 --- a/client/src/assets/player/shared/peertube/peertube-plugin.ts +++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts | |||
@@ -22,7 +22,7 @@ const Plugin = videojs.getPlugin('plugin') | |||
22 | 22 | ||
23 | class PeerTubePlugin extends Plugin { | 23 | class PeerTubePlugin extends Plugin { |
24 | private readonly videoViewUrl: string | 24 | private readonly videoViewUrl: string |
25 | private readonly authorizationHeader: string | 25 | private readonly authorizationHeader: () => string |
26 | 26 | ||
27 | private readonly videoUUID: string | 27 | private readonly videoUUID: string |
28 | private readonly startTime: number | 28 | private readonly startTime: number |
@@ -228,7 +228,7 @@ class PeerTubePlugin extends Plugin { | |||
228 | 'Content-type': 'application/json; charset=UTF-8' | 228 | 'Content-type': 'application/json; charset=UTF-8' |
229 | }) | 229 | }) |
230 | 230 | ||
231 | if (this.authorizationHeader) headers.set('Authorization', this.authorizationHeader) | 231 | if (this.authorizationHeader) headers.set('Authorization', this.authorizationHeader()) |
232 | 232 | ||
233 | return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers }) | 233 | return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers }) |
234 | } | 234 | } |
diff --git a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts index fa3f48a9a..658b7c867 100644 --- a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts | |||
@@ -2,7 +2,7 @@ import videojs from 'video.js' | |||
2 | import * as WebTorrent from 'webtorrent' | 2 | import * as WebTorrent from 'webtorrent' |
3 | import { logger } from '@root-helpers/logger' | 3 | import { logger } from '@root-helpers/logger' |
4 | import { isIOS } from '@root-helpers/web-browser' | 4 | import { isIOS } from '@root-helpers/web-browser' |
5 | import { timeToInt } from '@shared/core-utils' | 5 | import { addQueryParams, timeToInt } from '@shared/core-utils' |
6 | import { VideoFile } from '@shared/models' | 6 | import { VideoFile } from '@shared/models' |
7 | import { getAverageBandwidthInStore, getStoredMute, getStoredVolume, saveAverageBandwidth } from '../../peertube-player-local-storage' | 7 | import { getAverageBandwidthInStore, getStoredMute, getStoredVolume, saveAverageBandwidth } from '../../peertube-player-local-storage' |
8 | import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../../types' | 8 | import { PeerTubeResolution, PlayerNetworkInfo, WebtorrentPluginOptions } from '../../types' |
@@ -38,6 +38,8 @@ class WebTorrentPlugin extends Plugin { | |||
38 | BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5 // Last 5 seconds to build average bandwidth | 38 | BANDWIDTH_AVERAGE_NUMBER_OF_VALUES: 5 // Last 5 seconds to build average bandwidth |
39 | } | 39 | } |
40 | 40 | ||
41 | private readonly buildWebSeedUrls: (file: VideoFile) => string[] | ||
42 | |||
41 | private readonly webtorrent = new WebTorrent({ | 43 | private readonly webtorrent = new WebTorrent({ |
42 | tracker: { | 44 | tracker: { |
43 | rtcConfig: getRtcConfig() | 45 | rtcConfig: getRtcConfig() |
@@ -57,6 +59,9 @@ class WebTorrentPlugin extends Plugin { | |||
57 | private isAutoResolutionObservation = false | 59 | private isAutoResolutionObservation = false |
58 | private playerRefusedP2P = false | 60 | private playerRefusedP2P = false |
59 | 61 | ||
62 | private requiresAuth: boolean | ||
63 | private videoFileToken: () => string | ||
64 | |||
60 | private torrentInfoInterval: any | 65 | private torrentInfoInterval: any |
61 | private autoQualityInterval: any | 66 | private autoQualityInterval: any |
62 | private addTorrentDelay: any | 67 | private addTorrentDelay: any |
@@ -81,6 +86,11 @@ class WebTorrentPlugin extends Plugin { | |||
81 | this.savePlayerSrcFunction = this.player.src | 86 | this.savePlayerSrcFunction = this.player.src |
82 | this.playerElement = options.playerElement | 87 | this.playerElement = options.playerElement |
83 | 88 | ||
89 | this.requiresAuth = options.requiresAuth | ||
90 | this.videoFileToken = options.videoFileToken | ||
91 | |||
92 | this.buildWebSeedUrls = options.buildWebSeedUrls | ||
93 | |||
84 | this.player.ready(() => { | 94 | this.player.ready(() => { |
85 | const playerOptions = this.player.options_ | 95 | const playerOptions = this.player.options_ |
86 | 96 | ||
@@ -268,7 +278,8 @@ class WebTorrentPlugin extends Plugin { | |||
268 | return new CacheChunkStore(new PeertubeChunkStore(chunkLength, storeOpts), { | 278 | return new CacheChunkStore(new PeertubeChunkStore(chunkLength, storeOpts), { |
269 | max: 100 | 279 | max: 100 |
270 | }) | 280 | }) |
271 | } | 281 | }, |
282 | urlList: this.buildWebSeedUrls(this.currentVideoFile) | ||
272 | } | 283 | } |
273 | 284 | ||
274 | this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => { | 285 | this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => { |
@@ -533,7 +544,12 @@ class WebTorrentPlugin extends Plugin { | |||
533 | // Enable error display now this is our last fallback | 544 | // Enable error display now this is our last fallback |
534 | this.player.one('error', () => this.player.peertube().displayFatalError()) | 545 | this.player.one('error', () => this.player.peertube().displayFatalError()) |
535 | 546 | ||
536 | const httpUrl = this.currentVideoFile.fileUrl | 547 | let httpUrl = this.currentVideoFile.fileUrl |
548 | |||
549 | if (this.requiresAuth && this.videoFileToken) { | ||
550 | httpUrl = addQueryParams(httpUrl, { videoFileToken: this.videoFileToken() }) | ||
551 | } | ||
552 | |||
537 | this.player.src = this.savePlayerSrcFunction | 553 | this.player.src = this.savePlayerSrcFunction |
538 | this.player.src(httpUrl) | 554 | this.player.src(httpUrl) |
539 | 555 | ||
diff --git a/client/src/assets/player/types/manager-options.ts b/client/src/assets/player/types/manager-options.ts index b4d9374c3..9da8fedf8 100644 --- a/client/src/assets/player/types/manager-options.ts +++ b/client/src/assets/player/types/manager-options.ts | |||
@@ -57,7 +57,7 @@ export interface CommonOptions extends CustomizationOptions { | |||
57 | captions: boolean | 57 | captions: boolean |
58 | 58 | ||
59 | videoViewUrl: string | 59 | videoViewUrl: string |
60 | authorizationHeader?: string | 60 | authorizationHeader?: () => string |
61 | 61 | ||
62 | metricsUrl: string | 62 | metricsUrl: string |
63 | 63 | ||
@@ -77,6 +77,8 @@ export interface CommonOptions extends CustomizationOptions { | |||
77 | videoShortUUID: string | 77 | videoShortUUID: string |
78 | 78 | ||
79 | serverUrl: string | 79 | serverUrl: string |
80 | requiresAuth: boolean | ||
81 | videoFileToken: () => string | ||
80 | 82 | ||
81 | errorNotifier: (message: string) => void | 83 | errorNotifier: (message: string) => void |
82 | } | 84 | } |
diff --git a/client/src/assets/player/types/peertube-videojs-typings.ts b/client/src/assets/player/types/peertube-videojs-typings.ts index 6df94992c..037c4b74b 100644 --- a/client/src/assets/player/types/peertube-videojs-typings.ts +++ b/client/src/assets/player/types/peertube-videojs-typings.ts | |||
@@ -95,7 +95,7 @@ type PeerTubePluginOptions = { | |||
95 | videoDuration: number | 95 | videoDuration: number |
96 | 96 | ||
97 | videoViewUrl: string | 97 | videoViewUrl: string |
98 | authorizationHeader?: string | 98 | authorizationHeader?: () => string |
99 | 99 | ||
100 | subtitle?: string | 100 | subtitle?: string |
101 | 101 | ||
@@ -151,6 +151,11 @@ type WebtorrentPluginOptions = { | |||
151 | startTime: number | string | 151 | startTime: number | string |
152 | 152 | ||
153 | playerRefusedP2P: boolean | 153 | playerRefusedP2P: boolean |
154 | |||
155 | requiresAuth: boolean | ||
156 | videoFileToken: () => string | ||
157 | |||
158 | buildWebSeedUrls: (file: VideoFile) => string[] | ||
154 | } | 159 | } |
155 | 160 | ||
156 | type P2PMediaLoaderPluginOptions = { | 161 | type P2PMediaLoaderPluginOptions = { |
diff --git a/client/src/root-helpers/logger.ts b/client/src/root-helpers/logger.ts index 0d486c433..d1fdf73aa 100644 --- a/client/src/root-helpers/logger.ts +++ b/client/src/root-helpers/logger.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { ClientLogCreate } from '@shared/models/server' | 1 | import { ClientLogCreate } from '@shared/models/server' |
2 | import { peertubeLocalStorage } from './peertube-web-storage' | 2 | import { peertubeLocalStorage } from './peertube-web-storage' |
3 | import { UserTokens } from './users' | 3 | import { OAuthUserTokens } from './users' |
4 | 4 | ||
5 | export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void | 5 | export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void |
6 | export type LoggerLevel = 'info' | 'warn' | 'error' | 6 | export type LoggerLevel = 'info' | 'warn' | 'error' |
@@ -56,7 +56,7 @@ class Logger { | |||
56 | }) | 56 | }) |
57 | 57 | ||
58 | try { | 58 | try { |
59 | const tokens = UserTokens.getUserTokens(peertubeLocalStorage) | 59 | const tokens = OAuthUserTokens.getUserTokens(peertubeLocalStorage) |
60 | 60 | ||
61 | if (tokens) headers.set('Authorization', `${tokens.tokenType} ${tokens.accessToken}`) | 61 | if (tokens) headers.set('Authorization', `${tokens.tokenType} ${tokens.accessToken}`) |
62 | } catch (err) { | 62 | } catch (err) { |
diff --git a/client/src/root-helpers/users/index.ts b/client/src/root-helpers/users/index.ts index 2b11d0b7e..c03e67325 100644 --- a/client/src/root-helpers/users/index.ts +++ b/client/src/root-helpers/users/index.ts | |||
@@ -1,2 +1,2 @@ | |||
1 | export * from './user-local-storage-keys' | 1 | export * from './user-local-storage-keys' |
2 | export * from './user-tokens' | 2 | export * from './oauth-user-tokens' |
diff --git a/client/src/root-helpers/users/user-tokens.ts b/client/src/root-helpers/users/oauth-user-tokens.ts index a6d614cb7..a24e76b91 100644 --- a/client/src/root-helpers/users/user-tokens.ts +++ b/client/src/root-helpers/users/oauth-user-tokens.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { UserTokenLocalStorageKeys } from './user-local-storage-keys' | 1 | import { UserTokenLocalStorageKeys } from './user-local-storage-keys' |
2 | 2 | ||
3 | export class UserTokens { | 3 | export class OAuthUserTokens { |
4 | accessToken: string | 4 | accessToken: string |
5 | refreshToken: string | 5 | refreshToken: string |
6 | tokenType: string | 6 | tokenType: string |
7 | 7 | ||
8 | constructor (hash?: Partial<UserTokens>) { | 8 | constructor (hash?: Partial<OAuthUserTokens>) { |
9 | if (hash) { | 9 | if (hash) { |
10 | this.accessToken = hash.accessToken | 10 | this.accessToken = hash.accessToken |
11 | this.refreshToken = hash.refreshToken | 11 | this.refreshToken = hash.refreshToken |
@@ -25,14 +25,14 @@ export class UserTokens { | |||
25 | 25 | ||
26 | if (!accessTokenLocalStorage || !refreshTokenLocalStorage || !tokenTypeLocalStorage) return null | 26 | if (!accessTokenLocalStorage || !refreshTokenLocalStorage || !tokenTypeLocalStorage) return null |
27 | 27 | ||
28 | return new UserTokens({ | 28 | return new OAuthUserTokens({ |
29 | accessToken: accessTokenLocalStorage, | 29 | accessToken: accessTokenLocalStorage, |
30 | refreshToken: refreshTokenLocalStorage, | 30 | refreshToken: refreshTokenLocalStorage, |
31 | tokenType: tokenTypeLocalStorage | 31 | tokenType: tokenTypeLocalStorage |
32 | }) | 32 | }) |
33 | } | 33 | } |
34 | 34 | ||
35 | static saveToLocalStorage (localStorage: Pick<Storage, 'setItem'>, tokens: UserTokens) { | 35 | static saveToLocalStorage (localStorage: Pick<Storage, 'setItem'>, tokens: OAuthUserTokens) { |
36 | localStorage.setItem(UserTokenLocalStorageKeys.ACCESS_TOKEN, tokens.accessToken) | 36 | localStorage.setItem(UserTokenLocalStorageKeys.ACCESS_TOKEN, tokens.accessToken) |
37 | localStorage.setItem(UserTokenLocalStorageKeys.REFRESH_TOKEN, tokens.refreshToken) | 37 | localStorage.setItem(UserTokenLocalStorageKeys.REFRESH_TOKEN, tokens.refreshToken) |
38 | localStorage.setItem(UserTokenLocalStorageKeys.TOKEN_TYPE, tokens.tokenType) | 38 | localStorage.setItem(UserTokenLocalStorageKeys.TOKEN_TYPE, tokens.tokenType) |
diff --git a/client/src/root-helpers/video.ts b/client/src/root-helpers/video.ts index ba84e49ea..107ba1eba 100644 --- a/client/src/root-helpers/video.ts +++ b/client/src/root-helpers/video.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { HTMLServerConfig, Video } from '@shared/models' | 1 | import { HTMLServerConfig, Video, VideoPrivacy } from '@shared/models' |
2 | 2 | ||
3 | function buildVideoOrPlaylistEmbed (options: { | 3 | function buildVideoOrPlaylistEmbed (options: { |
4 | embedUrl: string | 4 | embedUrl: string |
@@ -26,9 +26,14 @@ function isP2PEnabled (video: Video, config: HTMLServerConfig, userP2PEnabled: b | |||
26 | return userP2PEnabled | 26 | return userP2PEnabled |
27 | } | 27 | } |
28 | 28 | ||
29 | function videoRequiresAuth (video: Video) { | ||
30 | return new Set([ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]).has(video.privacy.id) | ||
31 | } | ||
32 | |||
29 | export { | 33 | export { |
30 | buildVideoOrPlaylistEmbed, | 34 | buildVideoOrPlaylistEmbed, |
31 | isP2PEnabled | 35 | isP2PEnabled, |
36 | videoRequiresAuth | ||
32 | } | 37 | } |
33 | 38 | ||
34 | // --------------------------------------------------------------------------- | 39 | // --------------------------------------------------------------------------- |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 451e54840..c6160151a 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -6,7 +6,7 @@ import { peertubeTranslate } from '../../../../shared/core-utils/i18n' | |||
6 | import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models' | 6 | import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models' |
7 | import { PeertubePlayerManager } from '../../assets/player' | 7 | import { PeertubePlayerManager } from '../../assets/player' |
8 | import { TranslationsManager } from '../../assets/player/translations-manager' | 8 | import { TranslationsManager } from '../../assets/player/translations-manager' |
9 | import { getParamString, logger } from '../../root-helpers' | 9 | import { getParamString, logger, videoRequiresAuth } from '../../root-helpers' |
10 | import { PeerTubeEmbedApi } from './embed-api' | 10 | import { PeerTubeEmbedApi } from './embed-api' |
11 | import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared' | 11 | import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared' |
12 | import { PlayerHTML } from './shared/player-html' | 12 | import { PlayerHTML } from './shared/player-html' |
@@ -167,22 +167,25 @@ export class PeerTubeEmbed { | |||
167 | private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) { | 167 | private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) { |
168 | const alreadyHadPlayer = this.resetPlayerElement() | 168 | const alreadyHadPlayer = this.resetPlayerElement() |
169 | 169 | ||
170 | const videoInfoPromise: Promise<{ video: VideoDetails, live?: LiveVideo }> = videoResponse.json() | 170 | const videoInfoPromise = videoResponse.json() |
171 | .then((videoInfo: VideoDetails) => { | 171 | .then(async (videoInfo: VideoDetails) => { |
172 | this.playerManagerOptions.loadParams(this.config, videoInfo) | 172 | this.playerManagerOptions.loadParams(this.config, videoInfo) |
173 | 173 | ||
174 | if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) { | 174 | if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) { |
175 | this.playerHTML.buildPlaceholder(videoInfo) | 175 | this.playerHTML.buildPlaceholder(videoInfo) |
176 | } | 176 | } |
177 | const live = videoInfo.isLive | ||
178 | ? await this.videoFetcher.loadLive(videoInfo) | ||
179 | : undefined | ||
177 | 180 | ||
178 | if (!videoInfo.isLive) { | 181 | const videoFileToken = videoRequiresAuth(videoInfo) |
179 | return { video: videoInfo } | 182 | ? await this.videoFetcher.loadVideoToken(videoInfo) |
180 | } | 183 | : undefined |
181 | 184 | ||
182 | return this.videoFetcher.loadVideoWithLive(videoInfo) | 185 | return { live, video: videoInfo, videoFileToken } |
183 | }) | 186 | }) |
184 | 187 | ||
185 | const [ { video, live }, translations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([ | 188 | const [ { video, live, videoFileToken }, translations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([ |
186 | videoInfoPromise, | 189 | videoInfoPromise, |
187 | this.translationsPromise, | 190 | this.translationsPromise, |
188 | captionsPromise, | 191 | captionsPromise, |
@@ -200,6 +203,9 @@ export class PeerTubeEmbed { | |||
200 | translations, | 203 | translations, |
201 | serverConfig: this.config, | 204 | serverConfig: this.config, |
202 | 205 | ||
206 | authorizationHeader: () => this.http.getHeaderTokenValue(), | ||
207 | videoFileToken: () => videoFileToken, | ||
208 | |||
203 | onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer(uuid), | 209 | onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer(uuid), |
204 | 210 | ||
205 | playlistTracker: this.playlistTracker, | 211 | playlistTracker: this.playlistTracker, |
diff --git a/client/src/standalone/videos/shared/auth-http.ts b/client/src/standalone/videos/shared/auth-http.ts index 0356ab8a6..43af5dff4 100644 --- a/client/src/standalone/videos/shared/auth-http.ts +++ b/client/src/standalone/videos/shared/auth-http.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { HttpStatusCode, OAuth2ErrorCode, UserRefreshToken } from '../../../../../shared/models' | 1 | import { HttpStatusCode, OAuth2ErrorCode, UserRefreshToken } from '../../../../../shared/models' |
2 | import { objectToUrlEncoded, UserTokens } from '../../../root-helpers' | 2 | import { OAuthUserTokens, objectToUrlEncoded } from '../../../root-helpers' |
3 | import { peertubeLocalStorage } from '../../../root-helpers/peertube-web-storage' | 3 | import { peertubeLocalStorage } from '../../../root-helpers/peertube-web-storage' |
4 | 4 | ||
5 | export class AuthHTTP { | 5 | export class AuthHTTP { |
@@ -8,30 +8,30 @@ export class AuthHTTP { | |||
8 | CLIENT_SECRET: 'client_secret' | 8 | CLIENT_SECRET: 'client_secret' |
9 | } | 9 | } |
10 | 10 | ||
11 | private userTokens: UserTokens | 11 | private userOAuthTokens: OAuthUserTokens |
12 | 12 | ||
13 | private headers = new Headers() | 13 | private headers = new Headers() |
14 | 14 | ||
15 | constructor () { | 15 | constructor () { |
16 | this.userTokens = UserTokens.getUserTokens(peertubeLocalStorage) | 16 | this.userOAuthTokens = OAuthUserTokens.getUserTokens(peertubeLocalStorage) |
17 | 17 | ||
18 | if (this.userTokens) this.setHeadersFromTokens() | 18 | if (this.userOAuthTokens) this.setHeadersFromTokens() |
19 | } | 19 | } |
20 | 20 | ||
21 | fetch (url: string, { optionalAuth }: { optionalAuth: boolean }) { | 21 | fetch (url: string, { optionalAuth, method }: { optionalAuth: boolean, method?: string }) { |
22 | const refreshFetchOptions = optionalAuth | 22 | const refreshFetchOptions = optionalAuth |
23 | ? { headers: this.headers } | 23 | ? { headers: this.headers } |
24 | : {} | 24 | : {} |
25 | 25 | ||
26 | return this.refreshFetch(url.toString(), refreshFetchOptions) | 26 | return this.refreshFetch(url.toString(), { ...refreshFetchOptions, method }) |
27 | } | 27 | } |
28 | 28 | ||
29 | getHeaderTokenValue () { | 29 | getHeaderTokenValue () { |
30 | return `${this.userTokens.tokenType} ${this.userTokens.accessToken}` | 30 | return `${this.userOAuthTokens.tokenType} ${this.userOAuthTokens.accessToken}` |
31 | } | 31 | } |
32 | 32 | ||
33 | isLoggedIn () { | 33 | isLoggedIn () { |
34 | return !!this.userTokens | 34 | return !!this.userOAuthTokens |
35 | } | 35 | } |
36 | 36 | ||
37 | private refreshFetch (url: string, options?: RequestInit) { | 37 | private refreshFetch (url: string, options?: RequestInit) { |
@@ -47,7 +47,7 @@ export class AuthHTTP { | |||
47 | headers.set('Content-Type', 'application/x-www-form-urlencoded') | 47 | headers.set('Content-Type', 'application/x-www-form-urlencoded') |
48 | 48 | ||
49 | const data = { | 49 | const data = { |
50 | refresh_token: this.userTokens.refreshToken, | 50 | refresh_token: this.userOAuthTokens.refreshToken, |
51 | client_id: clientId, | 51 | client_id: clientId, |
52 | client_secret: clientSecret, | 52 | client_secret: clientSecret, |
53 | response_type: 'code', | 53 | response_type: 'code', |
@@ -64,15 +64,15 @@ export class AuthHTTP { | |||
64 | return res.json() | 64 | return res.json() |
65 | }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => { | 65 | }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => { |
66 | if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) { | 66 | if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) { |
67 | UserTokens.flushLocalStorage(peertubeLocalStorage) | 67 | OAuthUserTokens.flushLocalStorage(peertubeLocalStorage) |
68 | this.removeTokensFromHeaders() | 68 | this.removeTokensFromHeaders() |
69 | 69 | ||
70 | return resolve() | 70 | return resolve() |
71 | } | 71 | } |
72 | 72 | ||
73 | this.userTokens.accessToken = obj.access_token | 73 | this.userOAuthTokens.accessToken = obj.access_token |
74 | this.userTokens.refreshToken = obj.refresh_token | 74 | this.userOAuthTokens.refreshToken = obj.refresh_token |
75 | UserTokens.saveToLocalStorage(peertubeLocalStorage, this.userTokens) | 75 | OAuthUserTokens.saveToLocalStorage(peertubeLocalStorage, this.userOAuthTokens) |
76 | 76 | ||
77 | this.setHeadersFromTokens() | 77 | this.setHeadersFromTokens() |
78 | 78 | ||
@@ -84,7 +84,7 @@ export class AuthHTTP { | |||
84 | 84 | ||
85 | return refreshingTokenPromise | 85 | return refreshingTokenPromise |
86 | .catch(() => { | 86 | .catch(() => { |
87 | UserTokens.flushLocalStorage(peertubeLocalStorage) | 87 | OAuthUserTokens.flushLocalStorage(peertubeLocalStorage) |
88 | 88 | ||
89 | this.removeTokensFromHeaders() | 89 | this.removeTokensFromHeaders() |
90 | }).then(() => fetch(url, { | 90 | }).then(() => fetch(url, { |
diff --git a/client/src/standalone/videos/shared/player-manager-options.ts b/client/src/standalone/videos/shared/player-manager-options.ts index eed821994..87a84975b 100644 --- a/client/src/standalone/videos/shared/player-manager-options.ts +++ b/client/src/standalone/videos/shared/player-manager-options.ts | |||
@@ -17,7 +17,8 @@ import { | |||
17 | isP2PEnabled, | 17 | isP2PEnabled, |
18 | logger, | 18 | logger, |
19 | peertubeLocalStorage, | 19 | peertubeLocalStorage, |
20 | UserLocalStorageKeys | 20 | UserLocalStorageKeys, |
21 | videoRequiresAuth | ||
21 | } from '../../../root-helpers' | 22 | } from '../../../root-helpers' |
22 | import { PeerTubePlugin } from './peertube-plugin' | 23 | import { PeerTubePlugin } from './peertube-plugin' |
23 | import { PlayerHTML } from './player-html' | 24 | import { PlayerHTML } from './player-html' |
@@ -154,6 +155,9 @@ export class PlayerManagerOptions { | |||
154 | captionsResponse: Response | 155 | captionsResponse: Response |
155 | live?: LiveVideo | 156 | live?: LiveVideo |
156 | 157 | ||
158 | authorizationHeader: () => string | ||
159 | videoFileToken: () => string | ||
160 | |||
157 | serverConfig: HTMLServerConfig | 161 | serverConfig: HTMLServerConfig |
158 | 162 | ||
159 | alreadyHadPlayer: boolean | 163 | alreadyHadPlayer: boolean |
@@ -169,9 +173,11 @@ export class PlayerManagerOptions { | |||
169 | video, | 173 | video, |
170 | captionsResponse, | 174 | captionsResponse, |
171 | alreadyHadPlayer, | 175 | alreadyHadPlayer, |
176 | videoFileToken, | ||
172 | translations, | 177 | translations, |
173 | playlistTracker, | 178 | playlistTracker, |
174 | live, | 179 | live, |
180 | authorizationHeader, | ||
175 | serverConfig | 181 | serverConfig |
176 | } = options | 182 | } = options |
177 | 183 | ||
@@ -227,6 +233,10 @@ export class PlayerManagerOptions { | |||
227 | embedUrl: window.location.origin + video.embedPath, | 233 | embedUrl: window.location.origin + video.embedPath, |
228 | embedTitle: video.name, | 234 | embedTitle: video.name, |
229 | 235 | ||
236 | requiresAuth: videoRequiresAuth(video), | ||
237 | authorizationHeader, | ||
238 | videoFileToken, | ||
239 | |||
230 | errorNotifier: () => { | 240 | errorNotifier: () => { |
231 | // Empty, we don't have a notifier in the embed | 241 | // Empty, we don't have a notifier in the embed |
232 | }, | 242 | }, |
diff --git a/client/src/standalone/videos/shared/video-fetcher.ts b/client/src/standalone/videos/shared/video-fetcher.ts index b42d622f9..cf6d12831 100644 --- a/client/src/standalone/videos/shared/video-fetcher.ts +++ b/client/src/standalone/videos/shared/video-fetcher.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { HttpStatusCode, LiveVideo, VideoDetails } from '../../../../../shared/models' | 1 | import { HttpStatusCode, LiveVideo, VideoDetails, VideoToken } from '../../../../../shared/models' |
2 | import { logger } from '../../../root-helpers' | 2 | import { logger } from '../../../root-helpers' |
3 | import { AuthHTTP } from './auth-http' | 3 | import { AuthHTTP } from './auth-http' |
4 | 4 | ||
@@ -36,10 +36,15 @@ export class VideoFetcher { | |||
36 | return { captionsPromise, videoResponse } | 36 | return { captionsPromise, videoResponse } |
37 | } | 37 | } |
38 | 38 | ||
39 | loadVideoWithLive (video: VideoDetails) { | 39 | loadLive (video: VideoDetails) { |
40 | return this.http.fetch(this.getLiveUrl(video.uuid), { optionalAuth: true }) | 40 | return this.http.fetch(this.getLiveUrl(video.uuid), { optionalAuth: true }) |
41 | .then(res => res.json()) | 41 | .then(res => res.json() as Promise<LiveVideo>) |
42 | .then((live: LiveVideo) => ({ video, live })) | 42 | } |
43 | |||
44 | loadVideoToken (video: VideoDetails) { | ||
45 | return this.http.fetch(this.getVideoTokenUrl(video.uuid), { optionalAuth: true, method: 'POST' }) | ||
46 | .then(res => res.json() as Promise<VideoToken>) | ||
47 | .then(token => token.files.token) | ||
43 | } | 48 | } |
44 | 49 | ||
45 | getVideoViewsUrl (videoUUID: string) { | 50 | getVideoViewsUrl (videoUUID: string) { |
@@ -61,4 +66,8 @@ export class VideoFetcher { | |||
61 | private getLiveUrl (videoId: string) { | 66 | private getLiveUrl (videoId: string) { |
62 | return window.location.origin + '/api/v1/videos/live/' + videoId | 67 | return window.location.origin + '/api/v1/videos/live/' + videoId |
63 | } | 68 | } |
69 | |||
70 | private getVideoTokenUrl (id: string) { | ||
71 | return this.getVideoUrl(id) + '/token' | ||
72 | } | ||
64 | } | 73 | } |