diff options
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 29 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/player-manager-options.ts | 6 | ||||
-rw-r--r-- | server/controllers/object-storage-proxy.ts | 15 | ||||
-rw-r--r-- | server/lib/object-storage/shared/object-storage-helpers.ts | 5 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 6 |
6 files changed, 38 insertions, 31 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 182beb67b..36506e20e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -16,11 +16,11 @@ | |||
16 | * Classic installation: `cd /var/www/peertube/peertube-latest && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production node dist/scripts/migrations/peertube-5.0.js` | 16 | * Classic installation: `cd /var/www/peertube/peertube-latest && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production node dist/scripts/migrations/peertube-5.0.js` |
17 | * Docker installation: `cd /var/www/peertube-docker && docker-compose exec -u peertube peertube node dist/scripts/migrations/peertube-5.0.js` | 17 | * Docker installation: `cd /var/www/peertube-docker && docker-compose exec -u peertube peertube node dist/scripts/migrations/peertube-5.0.js` |
18 | * Configuration changes (`config/production.yaml`): | 18 | * Configuration changes (`config/production.yaml`): |
19 | * There is a new `secrets.peertube` configuration. You must fill it before running PeerTube v5 | 19 | * There is a new `secrets.peertube` configuration. You must fill it before running PeerTube v5: https://github.com/Chocobozzz/PeerTube/blob/v5.0.0/config/production.yaml.example#L14 |
20 | * `object_storage.upload_acl` is now a parent key that you must update | 20 | * `object_storage.upload_acl` is now a parent key that you must update: https://github.com/Chocobozzz/PeerTube/blob/v5.0.0/config/production.yaml.example#L153 |
21 | * You must update your nginx configuration: | 21 | * You must update your nginx configuration: |
22 | * We introduced a new `location` for plugin websocket routes | 22 | * We introduced a new `location` for plugin websocket routes: https://github.com/Chocobozzz/PeerTube/blob/v5.0.0/support/nginx/peertube#L135 |
23 | * We introduced a new `location` for private videos files | 23 | * We introduced a new `location` for private videos files: https://github.com/Chocobozzz/PeerTube/blob/v5.0.0/support/nginx/peertube#L217 |
24 | 24 | ||
25 | ### Documentation | 25 | ### Documentation |
26 | 26 | ||
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 5bb3b43c2..d268f4762 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -90,7 +90,7 @@ export class PeerTubeEmbed { | |||
90 | 90 | ||
91 | if (!videoId) return | 91 | if (!videoId) return |
92 | 92 | ||
93 | return this.loadVideoAndBuildPlayer({ uuid: videoId, forceAutoplay: false }) | 93 | return this.loadVideoAndBuildPlayer({ uuid: videoId, autoplayFromPreviousVideo: false, forceAutoplay: false }) |
94 | } | 94 | } |
95 | 95 | ||
96 | private async initPlaylist () { | 96 | private async initPlaylist () { |
@@ -147,7 +147,7 @@ export class PeerTubeEmbed { | |||
147 | 147 | ||
148 | this.playlistTracker.setCurrentElement(next) | 148 | this.playlistTracker.setCurrentElement(next) |
149 | 149 | ||
150 | return this.loadVideoAndBuildPlayer({ uuid: next.video.uuid, forceAutoplay: false }) | 150 | return this.loadVideoAndBuildPlayer({ uuid: next.video.uuid, autoplayFromPreviousVideo: true, forceAutoplay: false }) |
151 | } | 151 | } |
152 | 152 | ||
153 | async playPreviousPlaylistVideo () { | 153 | async playPreviousPlaylistVideo () { |
@@ -159,7 +159,7 @@ export class PeerTubeEmbed { | |||
159 | 159 | ||
160 | this.playlistTracker.setCurrentElement(previous) | 160 | this.playlistTracker.setCurrentElement(previous) |
161 | 161 | ||
162 | await this.loadVideoAndBuildPlayer({ uuid: previous.video.uuid, forceAutoplay: false }) | 162 | await this.loadVideoAndBuildPlayer({ uuid: previous.video.uuid, autoplayFromPreviousVideo: true, forceAutoplay: false }) |
163 | } | 163 | } |
164 | 164 | ||
165 | getCurrentPlaylistPosition () { | 165 | getCurrentPlaylistPosition () { |
@@ -170,14 +170,15 @@ export class PeerTubeEmbed { | |||
170 | 170 | ||
171 | private async loadVideoAndBuildPlayer (options: { | 171 | private async loadVideoAndBuildPlayer (options: { |
172 | uuid: string | 172 | uuid: string |
173 | autoplayFromPreviousVideo: boolean | ||
173 | forceAutoplay: boolean | 174 | forceAutoplay: boolean |
174 | }) { | 175 | }) { |
175 | const { uuid, forceAutoplay } = options | 176 | const { uuid, autoplayFromPreviousVideo, forceAutoplay } = options |
176 | 177 | ||
177 | try { | 178 | try { |
178 | const { videoResponse, captionsPromise } = await this.videoFetcher.loadVideo(uuid) | 179 | const { videoResponse, captionsPromise } = await this.videoFetcher.loadVideo(uuid) |
179 | 180 | ||
180 | return this.buildVideoPlayer({ videoResponse, captionsPromise, forceAutoplay }) | 181 | return this.buildVideoPlayer({ videoResponse, captionsPromise, autoplayFromPreviousVideo, forceAutoplay }) |
181 | } catch (err) { | 182 | } catch (err) { |
182 | this.playerHTML.displayError(err.message, await this.translationsPromise) | 183 | this.playerHTML.displayError(err.message, await this.translationsPromise) |
183 | } | 184 | } |
@@ -186,17 +187,18 @@ export class PeerTubeEmbed { | |||
186 | private async buildVideoPlayer (options: { | 187 | private async buildVideoPlayer (options: { |
187 | videoResponse: Response | 188 | videoResponse: Response |
188 | captionsPromise: Promise<Response> | 189 | captionsPromise: Promise<Response> |
190 | autoplayFromPreviousVideo: boolean | ||
189 | forceAutoplay: boolean | 191 | forceAutoplay: boolean |
190 | }) { | 192 | }) { |
191 | const { videoResponse, captionsPromise, forceAutoplay } = options | 193 | const { videoResponse, captionsPromise, autoplayFromPreviousVideo, forceAutoplay } = options |
192 | 194 | ||
193 | const alreadyHadPlayer = this.resetPlayerElement() | 195 | this.resetPlayerElement() |
194 | 196 | ||
195 | const videoInfoPromise = videoResponse.json() | 197 | const videoInfoPromise = videoResponse.json() |
196 | .then(async (videoInfo: VideoDetails) => { | 198 | .then(async (videoInfo: VideoDetails) => { |
197 | this.playerManagerOptions.loadParams(this.config, videoInfo) | 199 | this.playerManagerOptions.loadParams(this.config, videoInfo) |
198 | 200 | ||
199 | if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) { | 201 | if (!autoplayFromPreviousVideo && !this.playerManagerOptions.hasAutoplay()) { |
200 | this.playerHTML.buildPlaceholder(videoInfo) | 202 | this.playerHTML.buildPlaceholder(videoInfo) |
201 | } | 203 | } |
202 | const live = videoInfo.isLive | 204 | const live = videoInfo.isLive |
@@ -224,14 +226,14 @@ export class PeerTubeEmbed { | |||
224 | const playerOptions = await this.playerManagerOptions.getPlayerOptions({ | 226 | const playerOptions = await this.playerManagerOptions.getPlayerOptions({ |
225 | video, | 227 | video, |
226 | captionsResponse, | 228 | captionsResponse, |
227 | alreadyHadPlayer, | 229 | autoplayFromPreviousVideo, |
228 | translations, | 230 | translations, |
229 | serverConfig: this.config, | 231 | serverConfig: this.config, |
230 | 232 | ||
231 | authorizationHeader: () => this.http.getHeaderTokenValue(), | 233 | authorizationHeader: () => this.http.getHeaderTokenValue(), |
232 | videoFileToken: () => videoFileToken, | 234 | videoFileToken: () => videoFileToken, |
233 | 235 | ||
234 | onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer({ uuid, forceAutoplay: false }), | 236 | onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer({ uuid, autoplayFromPreviousVideo: true, forceAutoplay: false }), |
235 | 237 | ||
236 | playlistTracker: this.playlistTracker, | 238 | playlistTracker: this.playlistTracker, |
237 | playNextPlaylistVideo: () => this.playNextPlaylistVideo(), | 239 | playNextPlaylistVideo: () => this.playNextPlaylistVideo(), |
@@ -277,7 +279,7 @@ export class PeerTubeEmbed { | |||
277 | video, | 279 | video, |
278 | onPublishedVideo: () => { | 280 | onPublishedVideo: () => { |
279 | this.liveManager.stopListeningForChanges(video) | 281 | this.liveManager.stopListeningForChanges(video) |
280 | this.loadVideoAndBuildPlayer({ uuid: video.uuid, forceAutoplay: true }) | 282 | this.loadVideoAndBuildPlayer({ uuid: video.uuid, autoplayFromPreviousVideo: false, forceAutoplay: true }) |
281 | } | 283 | } |
282 | }) | 284 | }) |
283 | 285 | ||
@@ -294,12 +296,9 @@ export class PeerTubeEmbed { | |||
294 | } | 296 | } |
295 | 297 | ||
296 | private resetPlayerElement () { | 298 | private resetPlayerElement () { |
297 | let alreadyHadPlayer = false | ||
298 | |||
299 | if (this.player) { | 299 | if (this.player) { |
300 | this.player.dispose() | 300 | this.player.dispose() |
301 | this.player = undefined | 301 | this.player = undefined |
302 | alreadyHadPlayer = true | ||
303 | } | 302 | } |
304 | 303 | ||
305 | const playerElement = document.createElement('video') | 304 | const playerElement = document.createElement('video') |
@@ -308,8 +307,6 @@ export class PeerTubeEmbed { | |||
308 | 307 | ||
309 | this.playerHTML.setPlayerElement(playerElement) | 308 | this.playerHTML.setPlayerElement(playerElement) |
310 | this.playerHTML.addPlayerElementToDOM() | 309 | this.playerHTML.addPlayerElementToDOM() |
311 | |||
312 | return alreadyHadPlayer | ||
313 | } | 310 | } |
314 | 311 | ||
315 | private async buildPlayerPlaylistUpnext () { | 312 | private async buildPlayerPlaylistUpnext () { |
diff --git a/client/src/standalone/videos/shared/player-manager-options.ts b/client/src/standalone/videos/shared/player-manager-options.ts index 9ec012369..b0bdb2dd9 100644 --- a/client/src/standalone/videos/shared/player-manager-options.ts +++ b/client/src/standalone/videos/shared/player-manager-options.ts | |||
@@ -162,7 +162,7 @@ export class PlayerManagerOptions { | |||
162 | 162 | ||
163 | serverConfig: HTMLServerConfig | 163 | serverConfig: HTMLServerConfig |
164 | 164 | ||
165 | alreadyHadPlayer: boolean | 165 | autoplayFromPreviousVideo: boolean |
166 | 166 | ||
167 | translations: Translations | 167 | translations: Translations |
168 | 168 | ||
@@ -174,7 +174,7 @@ export class PlayerManagerOptions { | |||
174 | const { | 174 | const { |
175 | video, | 175 | video, |
176 | captionsResponse, | 176 | captionsResponse, |
177 | alreadyHadPlayer, | 177 | autoplayFromPreviousVideo, |
178 | videoFileToken, | 178 | videoFileToken, |
179 | translations, | 179 | translations, |
180 | forceAutoplay, | 180 | forceAutoplay, |
@@ -189,7 +189,7 @@ export class PlayerManagerOptions { | |||
189 | const playerOptions: PeertubePlayerManagerOptions = { | 189 | const playerOptions: PeertubePlayerManagerOptions = { |
190 | common: { | 190 | common: { |
191 | // Autoplay in playlist mode | 191 | // Autoplay in playlist mode |
192 | autoplay: alreadyHadPlayer ? true : this.autoplay, | 192 | autoplay: autoplayFromPreviousVideo ? true : this.autoplay, |
193 | forceAutoplay, | 193 | forceAutoplay, |
194 | 194 | ||
195 | controls: this.controls, | 195 | controls: this.controls, |
diff --git a/server/controllers/object-storage-proxy.ts b/server/controllers/object-storage-proxy.ts index aa853a383..32b8d21da 100644 --- a/server/controllers/object-storage-proxy.ts +++ b/server/controllers/object-storage-proxy.ts | |||
@@ -15,6 +15,7 @@ import { | |||
15 | } from '@server/middlewares' | 15 | } from '@server/middlewares' |
16 | import { HttpStatusCode } from '@shared/models' | 16 | import { HttpStatusCode } from '@shared/models' |
17 | import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist' | 17 | import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist' |
18 | import { GetObjectCommandOutput } from '@aws-sdk/client-s3' | ||
18 | 19 | ||
19 | const objectStorageProxyRouter = express.Router() | 20 | const objectStorageProxyRouter = express.Router() |
20 | 21 | ||
@@ -46,11 +47,13 @@ async function proxifyWebTorrent (req: express.Request, res: express.Response) { | |||
46 | logger.debug('Proxifying WebTorrent file %s from object storage.', filename) | 47 | logger.debug('Proxifying WebTorrent file %s from object storage.', filename) |
47 | 48 | ||
48 | try { | 49 | try { |
49 | const stream = await getWebTorrentFileReadStream({ | 50 | const { response: s3Response, stream } = await getWebTorrentFileReadStream({ |
50 | filename, | 51 | filename, |
51 | rangeHeader: req.header('range') | 52 | rangeHeader: req.header('range') |
52 | }) | 53 | }) |
53 | 54 | ||
55 | setS3Headers(res, s3Response) | ||
56 | |||
54 | return stream.pipe(res) | 57 | return stream.pipe(res) |
55 | } catch (err) { | 58 | } catch (err) { |
56 | return handleObjectStorageFailure(res, err) | 59 | return handleObjectStorageFailure(res, err) |
@@ -65,12 +68,14 @@ async function proxifyHLS (req: express.Request, res: express.Response) { | |||
65 | logger.debug('Proxifying HLS file %s from object storage.', filename) | 68 | logger.debug('Proxifying HLS file %s from object storage.', filename) |
66 | 69 | ||
67 | try { | 70 | try { |
68 | const stream = await getHLSFileReadStream({ | 71 | const { response: s3Response, stream } = await getHLSFileReadStream({ |
69 | playlist: playlist.withVideo(video), | 72 | playlist: playlist.withVideo(video), |
70 | filename, | 73 | filename, |
71 | rangeHeader: req.header('range') | 74 | rangeHeader: req.header('range') |
72 | }) | 75 | }) |
73 | 76 | ||
77 | setS3Headers(res, s3Response) | ||
78 | |||
74 | const streamReplacer = filename.endsWith('.m3u8') && doReinjectVideoFileToken(req) | 79 | const streamReplacer = filename.endsWith('.m3u8') && doReinjectVideoFileToken(req) |
75 | ? new StreamReplacer(line => injectQueryToPlaylistUrls(line, buildReinjectVideoFileTokenQuery(req))) | 80 | ? new StreamReplacer(line => injectQueryToPlaylistUrls(line, buildReinjectVideoFileTokenQuery(req))) |
76 | : new PassThrough() | 81 | : new PassThrough() |
@@ -102,3 +107,9 @@ function handleObjectStorageFailure (res: express.Response, err: Error) { | |||
102 | type: err.name | 107 | type: err.name |
103 | }) | 108 | }) |
104 | } | 109 | } |
110 | |||
111 | function setS3Headers (res: express.Response, s3Response: GetObjectCommandOutput) { | ||
112 | if (s3Response.$metadata.httpStatusCode === HttpStatusCode.PARTIAL_CONTENT_206) { | ||
113 | res.status(HttpStatusCode.PARTIAL_CONTENT_206) | ||
114 | } | ||
115 | } | ||
diff --git a/server/lib/object-storage/shared/object-storage-helpers.ts b/server/lib/object-storage/shared/object-storage-helpers.ts index 3046d76bc..8dff08ab4 100644 --- a/server/lib/object-storage/shared/object-storage-helpers.ts +++ b/server/lib/object-storage/shared/object-storage-helpers.ts | |||
@@ -187,7 +187,10 @@ async function createObjectReadStream (options: { | |||
187 | 187 | ||
188 | const response = await getClient().send(command) | 188 | const response = await getClient().send(command) |
189 | 189 | ||
190 | return response.Body as Readable | 190 | return { |
191 | response, | ||
192 | stream: response.Body as Readable | ||
193 | } | ||
191 | } | 194 | } |
192 | 195 | ||
193 | // --------------------------------------------------------------------------- | 196 | // --------------------------------------------------------------------------- |
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index c2f9d424e..bfa7235a2 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -8128,17 +8128,13 @@ components: | |||
8128 | NotificationSettingValue: | 8128 | NotificationSettingValue: |
8129 | type: integer | 8129 | type: integer |
8130 | description: > | 8130 | description: > |
8131 | Notification type | 8131 | Notification type. One of the following values, or a sum of multiple values: |
8132 | 8132 | ||
8133 | - `0` NONE | 8133 | - `0` NONE |
8134 | 8134 | ||
8135 | - `1` WEB | 8135 | - `1` WEB |
8136 | 8136 | ||
8137 | - `2` EMAIL | 8137 | - `2` EMAIL |
8138 | enum: | ||
8139 | - 0 | ||
8140 | - 1 | ||
8141 | - 2 | ||
8142 | Notification: | 8138 | Notification: |
8143 | properties: | 8139 | properties: |
8144 | id: | 8140 | id: |