aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/standalone
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/standalone')
-rw-r--r--client/src/standalone/videos/embed-api.ts46
-rw-r--r--client/src/standalone/videos/embed.ts29
-rw-r--r--client/src/standalone/videos/shared/auth-http.ts30
-rw-r--r--client/src/standalone/videos/shared/peertube-plugin.ts1
-rw-r--r--client/src/standalone/videos/shared/player-manager-options.ts12
-rw-r--r--client/src/standalone/videos/shared/video-fetcher.ts17
6 files changed, 100 insertions, 35 deletions
diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts
index 2124b4711..a99f1edae 100644
--- a/client/src/standalone/videos/embed-api.ts
+++ b/client/src/standalone/videos/embed-api.ts
@@ -13,6 +13,12 @@ export class PeerTubeEmbedApi {
13 private isReady = false 13 private isReady = false
14 private resolutions: PeerTubeResolution[] = [] 14 private resolutions: PeerTubeResolution[] = []
15 15
16 private oldVideoElement: HTMLVideoElement
17 private videoElPlayListener: () => void
18 private videoElPauseListener: () => void
19 private videoElEndedListener: () => void
20 private videoElInterval: any
21
16 constructor (private readonly embed: PeerTubeEmbed) { 22 constructor (private readonly embed: PeerTubeEmbed) {
17 23
18 } 24 }
@@ -26,6 +32,11 @@ export class PeerTubeEmbedApi {
26 this.notifyReady() 32 this.notifyReady()
27 } 33 }
28 34
35 reInit () {
36 this.disposeStateTracking()
37 this.setupStateTracking()
38 }
39
29 private get element () { 40 private get element () {
30 return this.embed.getPlayerElement() 41 return this.embed.getPlayerElement()
31 } 42 }
@@ -101,7 +112,7 @@ export class PeerTubeEmbedApi {
101 private setupStateTracking () { 112 private setupStateTracking () {
102 let currentState: 'playing' | 'paused' | 'unstarted' | 'ended' = 'unstarted' 113 let currentState: 'playing' | 'paused' | 'unstarted' | 'ended' = 'unstarted'
103 114
104 setInterval(() => { 115 this.videoElInterval = setInterval(() => {
105 const position = this.element.currentTime 116 const position = this.element.currentTime
106 const volume = this.element.volume 117 const volume = this.element.volume
107 118
@@ -116,20 +127,29 @@ export class PeerTubeEmbedApi {
116 }) 127 })
117 }, 500) 128 }, 500)
118 129
119 this.element.addEventListener('play', ev => { 130 // ---------------------------------------------------------------------------
131
132 this.videoElPlayListener = () => {
120 currentState = 'playing' 133 currentState = 'playing'
121 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' }) 134 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
122 }) 135 }
136 this.element.addEventListener('play', this.videoElPlayListener)
123 137
124 this.element.addEventListener('pause', ev => { 138 this.videoElPauseListener = () => {
125 currentState = 'paused' 139 currentState = 'paused'
126 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' }) 140 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
127 }) 141 }
142 this.element.addEventListener('pause', this.videoElPauseListener)
128 143
129 this.element.addEventListener('ended', ev => { 144 this.videoElEndedListener = () => {
130 currentState = 'ended' 145 currentState = 'ended'
131 this.channel.notify({ method: 'playbackStatusChange', params: 'ended' }) 146 this.channel.notify({ method: 'playbackStatusChange', params: 'ended' })
132 }) 147 }
148 this.element.addEventListener('ended', this.videoElEndedListener)
149
150 this.oldVideoElement = this.element
151
152 // ---------------------------------------------------------------------------
133 153
134 // PeerTube specific capabilities 154 // PeerTube specific capabilities
135 this.embed.player.peertubeResolutions().on('resolutionsAdded', () => this.loadResolutions()) 155 this.embed.player.peertubeResolutions().on('resolutionsAdded', () => this.loadResolutions())
@@ -145,6 +165,18 @@ export class PeerTubeEmbedApi {
145 }) 165 })
146 } 166 }
147 167
168 private disposeStateTracking () {
169 if (!this.oldVideoElement) return
170
171 this.oldVideoElement.removeEventListener('play', this.videoElPlayListener)
172 this.oldVideoElement.removeEventListener('pause', this.videoElPauseListener)
173 this.oldVideoElement.removeEventListener('ended', this.videoElEndedListener)
174
175 clearInterval(this.videoElInterval)
176
177 this.oldVideoElement = undefined
178 }
179
148 private loadResolutions () { 180 private loadResolutions () {
149 this.resolutions = this.embed.player.peertubeResolutions().getResolutions() 181 this.resolutions = this.embed.player.peertubeResolutions().getResolutions()
150 .map(r => ({ 182 .map(r => ({
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 451e54840..356f149c0 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -3,10 +3,10 @@ import '../../assets/player/shared/dock/peertube-dock-component'
3import '../../assets/player/shared/dock/peertube-dock-plugin' 3import '../../assets/player/shared/dock/peertube-dock-plugin'
4import videojs from 'video.js' 4import videojs from 'video.js'
5import { peertubeTranslate } from '../../../../shared/core-utils/i18n' 5import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
6import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models' 6import { HTMLServerConfig, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models'
7import { PeertubePlayerManager } from '../../assets/player' 7import { PeertubePlayerManager } from '../../assets/player'
8import { TranslationsManager } from '../../assets/player/translations-manager' 8import { TranslationsManager } from '../../assets/player/translations-manager'
9import { getParamString, logger } from '../../root-helpers' 9import { getParamString, logger, videoRequiresAuth } from '../../root-helpers'
10import { PeerTubeEmbedApi } from './embed-api' 10import { PeerTubeEmbedApi } from './embed-api'
11import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared' 11import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared'
12import { PlayerHTML } from './shared/player-html' 12import { PlayerHTML } from './shared/player-html'
@@ -117,6 +117,11 @@ export class PeerTubeEmbed {
117 117
118 private initializeApi () { 118 private initializeApi () {
119 if (this.playerManagerOptions.hasAPIEnabled()) { 119 if (this.playerManagerOptions.hasAPIEnabled()) {
120 if (this.api) {
121 this.api.reInit()
122 return
123 }
124
120 this.api = new PeerTubeEmbedApi(this) 125 this.api = new PeerTubeEmbedApi(this)
121 this.api.initialize() 126 this.api.initialize()
122 } 127 }
@@ -167,22 +172,25 @@ export class PeerTubeEmbed {
167 private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) { 172 private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) {
168 const alreadyHadPlayer = this.resetPlayerElement() 173 const alreadyHadPlayer = this.resetPlayerElement()
169 174
170 const videoInfoPromise: Promise<{ video: VideoDetails, live?: LiveVideo }> = videoResponse.json() 175 const videoInfoPromise = videoResponse.json()
171 .then((videoInfo: VideoDetails) => { 176 .then(async (videoInfo: VideoDetails) => {
172 this.playerManagerOptions.loadParams(this.config, videoInfo) 177 this.playerManagerOptions.loadParams(this.config, videoInfo)
173 178
174 if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) { 179 if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) {
175 this.playerHTML.buildPlaceholder(videoInfo) 180 this.playerHTML.buildPlaceholder(videoInfo)
176 } 181 }
182 const live = videoInfo.isLive
183 ? await this.videoFetcher.loadLive(videoInfo)
184 : undefined
177 185
178 if (!videoInfo.isLive) { 186 const videoFileToken = videoRequiresAuth(videoInfo)
179 return { video: videoInfo } 187 ? await this.videoFetcher.loadVideoToken(videoInfo)
180 } 188 : undefined
181 189
182 return this.videoFetcher.loadVideoWithLive(videoInfo) 190 return { live, video: videoInfo, videoFileToken }
183 }) 191 })
184 192
185 const [ { video, live }, translations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([ 193 const [ { video, live, videoFileToken }, translations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([
186 videoInfoPromise, 194 videoInfoPromise,
187 this.translationsPromise, 195 this.translationsPromise,
188 captionsPromise, 196 captionsPromise,
@@ -200,6 +208,9 @@ export class PeerTubeEmbed {
200 translations, 208 translations,
201 serverConfig: this.config, 209 serverConfig: this.config,
202 210
211 authorizationHeader: () => this.http.getHeaderTokenValue(),
212 videoFileToken: () => videoFileToken,
213
203 onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer(uuid), 214 onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer(uuid),
204 215
205 playlistTracker: this.playlistTracker, 216 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..95e3b029e 100644
--- a/client/src/standalone/videos/shared/auth-http.ts
+++ b/client/src/standalone/videos/shared/auth-http.ts
@@ -1,5 +1,5 @@
1import { HttpStatusCode, OAuth2ErrorCode, UserRefreshToken } from '../../../../../shared/models' 1import { HttpStatusCode, OAuth2ErrorCode, UserRefreshToken } from '../../../../../shared/models'
2import { objectToUrlEncoded, UserTokens } from '../../../root-helpers' 2import { OAuthUserTokens, objectToUrlEncoded } from '../../../root-helpers'
3import { peertubeLocalStorage } from '../../../root-helpers/peertube-web-storage' 3import { peertubeLocalStorage } from '../../../root-helpers/peertube-web-storage'
4 4
5export class AuthHTTP { 5export class AuthHTTP {
@@ -8,30 +8,32 @@ 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 if (!this.userOAuthTokens) return null
31
32 return `${this.userOAuthTokens.tokenType} ${this.userOAuthTokens.accessToken}`
31 } 33 }
32 34
33 isLoggedIn () { 35 isLoggedIn () {
34 return !!this.userTokens 36 return !!this.userOAuthTokens
35 } 37 }
36 38
37 private refreshFetch (url: string, options?: RequestInit) { 39 private refreshFetch (url: string, options?: RequestInit) {
@@ -47,7 +49,7 @@ export class AuthHTTP {
47 headers.set('Content-Type', 'application/x-www-form-urlencoded') 49 headers.set('Content-Type', 'application/x-www-form-urlencoded')
48 50
49 const data = { 51 const data = {
50 refresh_token: this.userTokens.refreshToken, 52 refresh_token: this.userOAuthTokens.refreshToken,
51 client_id: clientId, 53 client_id: clientId,
52 client_secret: clientSecret, 54 client_secret: clientSecret,
53 response_type: 'code', 55 response_type: 'code',
@@ -64,15 +66,15 @@ export class AuthHTTP {
64 return res.json() 66 return res.json()
65 }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => { 67 }).then((obj: UserRefreshToken & { code?: OAuth2ErrorCode }) => {
66 if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) { 68 if (!obj || obj.code === OAuth2ErrorCode.INVALID_GRANT) {
67 UserTokens.flushLocalStorage(peertubeLocalStorage) 69 OAuthUserTokens.flushLocalStorage(peertubeLocalStorage)
68 this.removeTokensFromHeaders() 70 this.removeTokensFromHeaders()
69 71
70 return resolve() 72 return resolve()
71 } 73 }
72 74
73 this.userTokens.accessToken = obj.access_token 75 this.userOAuthTokens.accessToken = obj.access_token
74 this.userTokens.refreshToken = obj.refresh_token 76 this.userOAuthTokens.refreshToken = obj.refresh_token
75 UserTokens.saveToLocalStorage(peertubeLocalStorage, this.userTokens) 77 OAuthUserTokens.saveToLocalStorage(peertubeLocalStorage, this.userOAuthTokens)
76 78
77 this.setHeadersFromTokens() 79 this.setHeadersFromTokens()
78 80
@@ -84,7 +86,7 @@ export class AuthHTTP {
84 86
85 return refreshingTokenPromise 87 return refreshingTokenPromise
86 .catch(() => { 88 .catch(() => {
87 UserTokens.flushLocalStorage(peertubeLocalStorage) 89 OAuthUserTokens.flushLocalStorage(peertubeLocalStorage)
88 90
89 this.removeTokensFromHeaders() 91 this.removeTokensFromHeaders()
90 }).then(() => fetch(url, { 92 }).then(() => fetch(url, {
diff --git a/client/src/standalone/videos/shared/peertube-plugin.ts b/client/src/standalone/videos/shared/peertube-plugin.ts
index 968854ce8..daf6f2b03 100644
--- a/client/src/standalone/videos/shared/peertube-plugin.ts
+++ b/client/src/standalone/videos/shared/peertube-plugin.ts
@@ -43,6 +43,7 @@ export class PeerTubePlugin {
43 return { 43 return {
44 getBaseStaticRoute: unimplemented, 44 getBaseStaticRoute: unimplemented,
45 getBaseRouterRoute: unimplemented, 45 getBaseRouterRoute: unimplemented,
46 getBaseWebSocketRoute: unimplemented,
46 getBasePluginClientPath: unimplemented, 47 getBasePluginClientPath: unimplemented,
47 48
48 getSettings: () => { 49 getSettings: () => {
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'
22import { PeerTubePlugin } from './peertube-plugin' 23import { PeerTubePlugin } from './peertube-plugin'
23import { PlayerHTML } from './player-html' 24import { 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 @@
1import { HttpStatusCode, LiveVideo, VideoDetails } from '../../../../../shared/models' 1import { HttpStatusCode, LiveVideo, VideoDetails, VideoToken } from '../../../../../shared/models'
2import { logger } from '../../../root-helpers' 2import { logger } from '../../../root-helpers'
3import { AuthHTTP } from './auth-http' 3import { 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}