aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-03-04 13:40:02 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-03-09 09:23:10 +0100
commitf443a74649174b2f9347c158e30f8ac7aa3e958a (patch)
treee423bc4e2307477bda4341037b7fa04ad10adae6 /client
parent01dd04cd5ab7b55d2a9af7d0ebf405bee9579b09 (diff)
downloadPeerTube-f443a74649174b2f9347c158e30f8ac7aa3e958a.tar.gz
PeerTube-f443a74649174b2f9347c158e30f8ac7aa3e958a.tar.zst
PeerTube-f443a74649174b2f9347c158e30f8ac7aa3e958a.zip
Add latency setting support
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts3
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html12
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.html11
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.ts25
-rw-r--r--client/src/app/+videos/+video-edit/video-update.component.ts4
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts34
-rw-r--r--client/src/assets/player/peertube-player-options-builder.ts99
-rw-r--r--client/src/standalone/videos/embed.ts28
8 files changed, 184 insertions, 32 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index e3b6f8305..94f1021bf 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -189,6 +189,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
189 maxInstanceLives: MAX_INSTANCE_LIVES_VALIDATOR, 189 maxInstanceLives: MAX_INSTANCE_LIVES_VALIDATOR,
190 maxUserLives: MAX_USER_LIVES_VALIDATOR, 190 maxUserLives: MAX_USER_LIVES_VALIDATOR,
191 allowReplay: null, 191 allowReplay: null,
192 latencySetting: {
193 enabled: null
194 },
192 195
193 transcoding: { 196 transcoding: {
194 enabled: null, 197 enabled: null,
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html
index 10d5278c1..8d6a4ce19 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html
@@ -36,6 +36,18 @@
36 </my-peertube-checkbox> 36 </my-peertube-checkbox>
37 </div> 37 </div>
38 38
39 <div class="form-group" formGroupName="latencySetting" [ngClass]="getDisabledLiveClass()">
40 <my-peertube-checkbox
41 inputName="liveLatencySettingEnabled" formControlName="enabled"
42 i18n-labelText labelText="Allow your users to change live latency"
43 >
44 <ng-container ngProjectAs="description" i18n>
45 Small latency disables P2P and high latency can increase P2P ratio
46 </ng-container>
47
48 </my-peertube-checkbox>
49 </div>
50
39 <div class="form-group" [ngClass]="getDisabledLiveClass()"> 51 <div class="form-group" [ngClass]="getDisabledLiveClass()">
40 <label i18n for="liveMaxInstanceLives"> 52 <label i18n for="liveMaxInstanceLives">
41 Max simultaneous lives created on your instance <span class="text-muted">(-1 for "unlimited")</span> 53 Max simultaneous lives created on your instance <span class="text-muted">(-1 for "unlimited")</span>
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html
index 2281f8631..515daf15f 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html
@@ -289,6 +289,17 @@
289 </ng-container> 289 </ng-container>
290 </my-peertube-checkbox> 290 </my-peertube-checkbox>
291 </div> 291 </div>
292
293 <div class="form-group" *ngIf="isLatencyModeEnabled()">
294 <label i18n for="latencyMode">Latency mode</label>
295 <my-select-options
296 labelForId="latencyMode" [items]="latencyModes" formControlName="latencyMode" [clearable]="true"
297 ></my-select-options>
298
299 <div *ngIf="formErrors.latencyMode" class="form-error">
300 {{ formErrors.latencyMode }}
301 </div>
302 </div>
292 </div> 303 </div>
293 </div> 304 </div>
294 </ng-template> 305 </ng-template>
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
index 2801fc519..a2399eafb 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts
@@ -1,6 +1,6 @@
1import { forkJoin } from 'rxjs' 1import { forkJoin } from 'rxjs'
2import { map } from 'rxjs/operators' 2import { map } from 'rxjs/operators'
3import { SelectChannelItem } from 'src/types/select-options-item.model' 3import { SelectChannelItem, SelectOptionsItem } from 'src/types/select-options-item.model'
4import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' 4import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
5import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms' 5import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms'
6import { HooksService, PluginService, ServerService } from '@app/core' 6import { HooksService, PluginService, ServerService } from '@app/core'
@@ -26,6 +26,7 @@ import { PluginInfo } from '@root-helpers/plugins-manager'
26import { 26import {
27 HTMLServerConfig, 27 HTMLServerConfig,
28 LiveVideo, 28 LiveVideo,
29 LiveVideoLatencyMode,
29 RegisterClientFormFieldOptions, 30 RegisterClientFormFieldOptions,
30 RegisterClientVideoFieldOptions, 31 RegisterClientVideoFieldOptions,
31 VideoConstant, 32 VideoConstant,
@@ -78,6 +79,23 @@ export class VideoEditComponent implements OnInit, OnDestroy {
78 videoCategories: VideoConstant<number>[] = [] 79 videoCategories: VideoConstant<number>[] = []
79 videoLicences: VideoConstant<number>[] = [] 80 videoLicences: VideoConstant<number>[] = []
80 videoLanguages: VideoLanguages[] = [] 81 videoLanguages: VideoLanguages[] = []
82 latencyModes: SelectOptionsItem[] = [
83 {
84 id: LiveVideoLatencyMode.SMALL_LATENCY,
85 label: $localize`Small latency`,
86 description: $localize`Reduce latency to ~15s disabling P2P`
87 },
88 {
89 id: LiveVideoLatencyMode.DEFAULT,
90 label: $localize`Default`,
91 description: $localize`Average latency of 30s`
92 },
93 {
94 id: LiveVideoLatencyMode.HIGH_LATENCY,
95 label: $localize`High latency`,
96 description: $localize`Average latency of 60s increasing P2P ratio`
97 }
98 ]
81 99
82 pluginDataFormGroup: FormGroup 100 pluginDataFormGroup: FormGroup
83 101
@@ -141,6 +159,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
141 originallyPublishedAt: VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR, 159 originallyPublishedAt: VIDEO_ORIGINALLY_PUBLISHED_AT_VALIDATOR,
142 liveStreamKey: null, 160 liveStreamKey: null,
143 permanentLive: null, 161 permanentLive: null,
162 latencyMode: null,
144 saveReplay: null 163 saveReplay: null
145 } 164 }
146 165
@@ -273,6 +292,10 @@ export class VideoEditComponent implements OnInit, OnDestroy {
273 return this.form.value['permanentLive'] === true 292 return this.form.value['permanentLive'] === true
274 } 293 }
275 294
295 isLatencyModeEnabled () {
296 return this.serverConfig.live.latencySetting.enabled
297 }
298
276 isPluginFieldHidden (pluginField: PluginField) { 299 isPluginFieldHidden (pluginField: PluginField) {
277 if (typeof pluginField.commonOptions.hidden !== 'function') return false 300 if (typeof pluginField.commonOptions.hidden !== 'function') return false
278 301
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts
index d9e8344fc..9c4998f2e 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.ts
+++ b/client/src/app/+videos/+video-edit/video-update.component.ts
@@ -64,6 +64,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
64 if (this.liveVideo) { 64 if (this.liveVideo) {
65 this.form.patchValue({ 65 this.form.patchValue({
66 saveReplay: this.liveVideo.saveReplay, 66 saveReplay: this.liveVideo.saveReplay,
67 latencyMode: this.liveVideo.latencyMode,
67 permanentLive: this.liveVideo.permanentLive 68 permanentLive: this.liveVideo.permanentLive
68 }) 69 })
69 } 70 }
@@ -127,7 +128,8 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
127 128
128 const liveVideoUpdate: LiveVideoUpdate = { 129 const liveVideoUpdate: LiveVideoUpdate = {
129 saveReplay: !!this.form.value.saveReplay, 130 saveReplay: !!this.form.value.saveReplay,
130 permanentLive: !!this.form.value.permanentLive 131 permanentLive: !!this.form.value.permanentLive,
132 latencyMode: this.form.value.latencyMode
131 } 133 }
132 134
133 // Don't update live attributes if they did not change 135 // Don't update live attributes if they did not change
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 1f45c4d26..067d3bc84 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -1,5 +1,5 @@
1import { Hotkey, HotkeysService } from 'angular2-hotkeys' 1import { Hotkey, HotkeysService } from 'angular2-hotkeys'
2import { forkJoin, Subscription } from 'rxjs' 2import { forkJoin, map, Observable, of, Subscription, switchMap } from 'rxjs'
3import { isP2PEnabled } from 'src/assets/player/utils' 3import { isP2PEnabled } from 'src/assets/player/utils'
4import { PlatformLocation } from '@angular/common' 4import { PlatformLocation } from '@angular/common'
5import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' 5import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
@@ -22,11 +22,13 @@ import { HooksService } from '@app/core/plugins/hooks.service'
22import { isXPercentInViewport, scrollToTop } from '@app/helpers' 22import { isXPercentInViewport, scrollToTop } from '@app/helpers'
23import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main' 23import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main'
24import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' 24import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
25import { LiveVideoService } from '@app/shared/shared-video-live'
25import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' 26import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
26import { timeToInt } from '@shared/core-utils' 27import { timeToInt } from '@shared/core-utils'
27import { 28import {
28 HTMLServerConfig, 29 HTMLServerConfig,
29 HttpStatusCode, 30 HttpStatusCode,
31 LiveVideo,
30 PeerTubeProblemDocument, 32 PeerTubeProblemDocument,
31 ServerErrorCode, 33 ServerErrorCode,
32 VideoCaption, 34 VideoCaption,
@@ -63,6 +65,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
63 65
64 video: VideoDetails = null 66 video: VideoDetails = null
65 videoCaptions: VideoCaption[] = [] 67 videoCaptions: VideoCaption[] = []
68 liveVideo: LiveVideo
66 69
67 playlistPosition: number 70 playlistPosition: number
68 playlist: VideoPlaylist = null 71 playlist: VideoPlaylist = null
@@ -89,6 +92,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
89 private router: Router, 92 private router: Router,
90 private videoService: VideoService, 93 private videoService: VideoService,
91 private playlistService: VideoPlaylistService, 94 private playlistService: VideoPlaylistService,
95 private liveVideoService: LiveVideoService,
92 private confirmService: ConfirmService, 96 private confirmService: ConfirmService,
93 private metaService: MetaService, 97 private metaService: MetaService,
94 private authService: AuthService, 98 private authService: AuthService,
@@ -239,12 +243,21 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
239 'filter:api.video-watch.video.get.result' 243 'filter:api.video-watch.video.get.result'
240 ) 244 )
241 245
246 const videoAndLiveObs: Observable<{ video: VideoDetails, live?: LiveVideo }> = videoObs.pipe(
247 switchMap(video => {
248 if (!video.isLive) return of({ video })
249
250 return this.liveVideoService.getVideoLive(video.uuid)
251 .pipe(map(live => ({ live, video })))
252 })
253 )
254
242 forkJoin([ 255 forkJoin([
243 videoObs, 256 videoAndLiveObs,
244 this.videoCaptionService.listCaptions(videoId), 257 this.videoCaptionService.listCaptions(videoId),
245 this.userService.getAnonymousOrLoggedUser() 258 this.userService.getAnonymousOrLoggedUser()
246 ]).subscribe({ 259 ]).subscribe({
247 next: ([ video, captionsResult, loggedInOrAnonymousUser ]) => { 260 next: ([ { video, live }, captionsResult, loggedInOrAnonymousUser ]) => {
248 const queryParams = this.route.snapshot.queryParams 261 const queryParams = this.route.snapshot.queryParams
249 262
250 const urlOptions = { 263 const urlOptions = {
@@ -261,7 +274,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
261 peertubeLink: false 274 peertubeLink: false
262 } 275 }
263 276
264 this.onVideoFetched({ video, videoCaptions: captionsResult.data, loggedInOrAnonymousUser, urlOptions }) 277 this.onVideoFetched({ video, live, videoCaptions: captionsResult.data, loggedInOrAnonymousUser, urlOptions })
265 .catch(err => this.handleGlobalError(err)) 278 .catch(err => this.handleGlobalError(err))
266 }, 279 },
267 280
@@ -330,16 +343,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
330 343
331 private async onVideoFetched (options: { 344 private async onVideoFetched (options: {
332 video: VideoDetails 345 video: VideoDetails
346 live: LiveVideo
333 videoCaptions: VideoCaption[] 347 videoCaptions: VideoCaption[]
334 urlOptions: URLOptions 348 urlOptions: URLOptions
335 loggedInOrAnonymousUser: User 349 loggedInOrAnonymousUser: User
336 }) { 350 }) {
337 const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser } = options 351 const { video, live, videoCaptions, urlOptions, loggedInOrAnonymousUser } = options
338 352
339 this.subscribeToLiveEventsIfNeeded(this.video, video) 353 this.subscribeToLiveEventsIfNeeded(this.video, video)
340 354
341 this.video = video 355 this.video = video
342 this.videoCaptions = videoCaptions 356 this.videoCaptions = videoCaptions
357 this.liveVideo = live
343 358
344 // Re init attributes 359 // Re init attributes
345 this.playerPlaceholderImgSrc = undefined 360 this.playerPlaceholderImgSrc = undefined
@@ -387,6 +402,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
387 const params = { 402 const params = {
388 video: this.video, 403 video: this.video,
389 videoCaptions: this.videoCaptions, 404 videoCaptions: this.videoCaptions,
405 liveVideo: this.liveVideo,
390 urlOptions, 406 urlOptions,
391 loggedInOrAnonymousUser, 407 loggedInOrAnonymousUser,
392 user: this.user 408 user: this.user
@@ -532,12 +548,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
532 548
533 private buildPlayerManagerOptions (params: { 549 private buildPlayerManagerOptions (params: {
534 video: VideoDetails 550 video: VideoDetails
551 liveVideo: LiveVideo
535 videoCaptions: VideoCaption[] 552 videoCaptions: VideoCaption[]
536 urlOptions: CustomizationOptions & { playerMode: PlayerMode } 553 urlOptions: CustomizationOptions & { playerMode: PlayerMode }
537 loggedInOrAnonymousUser: User 554 loggedInOrAnonymousUser: User
538 user?: AuthUser 555 user?: AuthUser
539 }) { 556 }) {
540 const { video, videoCaptions, urlOptions, loggedInOrAnonymousUser, user } = params 557 const { video, liveVideo, videoCaptions, urlOptions, loggedInOrAnonymousUser, user } = params
541 558
542 const getStartTime = () => { 559 const getStartTime = () => {
543 const byUrl = urlOptions.startTime !== undefined 560 const byUrl = urlOptions.startTime !== undefined
@@ -562,6 +579,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
562 src: environment.apiUrl + c.captionPath 579 src: environment.apiUrl + c.captionPath
563 })) 580 }))
564 581
582 const liveOptions = video.isLive
583 ? { latencyMode: liveVideo.latencyMode }
584 : undefined
585
565 const options: PeertubePlayerManagerOptions = { 586 const options: PeertubePlayerManagerOptions = {
566 common: { 587 common: {
567 autoplay: this.isAutoplay(), 588 autoplay: this.isAutoplay(),
@@ -597,6 +618,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
597 embedTitle: video.name, 618 embedTitle: video.name,
598 619
599 isLive: video.isLive, 620 isLive: video.isLive,
621 liveOptions,
600 622
601 language: this.localeId, 623 language: this.localeId,
602 624
diff --git a/client/src/assets/player/peertube-player-options-builder.ts b/client/src/assets/player/peertube-player-options-builder.ts
index 7a82b128d..c9cbbbf4d 100644
--- a/client/src/assets/player/peertube-player-options-builder.ts
+++ b/client/src/assets/player/peertube-player-options-builder.ts
@@ -1,9 +1,10 @@
1import videojs from 'video.js' 1import videojs from 'video.js'
2import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
2import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs' 3import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
3import { PluginsManager } from '@root-helpers/plugins-manager' 4import { PluginsManager } from '@root-helpers/plugins-manager'
4import { buildVideoLink, decorateVideoLink } from '@shared/core-utils' 5import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
5import { isDefaultLocale } from '@shared/core-utils/i18n' 6import { isDefaultLocale } from '@shared/core-utils/i18n'
6import { VideoFile } from '@shared/models' 7import { LiveVideoLatencyMode, VideoFile } from '@shared/models'
7import { copyToClipboard } from '../../root-helpers/utils' 8import { copyToClipboard } from '../../root-helpers/utils'
8import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' 9import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
9import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' 10import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
@@ -19,7 +20,6 @@ import {
19 VideoJSPluginOptions 20 VideoJSPluginOptions
20} from './peertube-videojs-typings' 21} from './peertube-videojs-typings'
21import { buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils' 22import { buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils'
22import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
23 23
24export type PlayerMode = 'webtorrent' | 'p2p-media-loader' 24export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
25 25
@@ -76,6 +76,9 @@ export interface CommonOptions extends CustomizationOptions {
76 embedTitle: string 76 embedTitle: string
77 77
78 isLive: boolean 78 isLive: boolean
79 liveOptions?: {
80 latencyMode: LiveVideoLatencyMode
81 }
79 82
80 language?: string 83 language?: string
81 84
@@ -250,21 +253,8 @@ export class PeertubePlayerOptionsBuilder {
250 .filter(t => t.startsWith('ws')) 253 .filter(t => t.startsWith('ws'))
251 254
252 const specificLiveOrVODOptions = this.options.common.isLive 255 const specificLiveOrVODOptions = this.options.common.isLive
253 ? { // Live 256 ? this.getP2PMediaLoaderLiveOptions()
254 requiredSegmentsPriority: 1 257 : this.getP2PMediaLoaderVODOptions()
255 }
256 : { // VOD
257 requiredSegmentsPriority: 3,
258
259 cachedSegmentExpiration: 86400000,
260 cachedSegmentsCount: 100,
261
262 httpDownloadMaxPriority: 9,
263 httpDownloadProbability: 0.06,
264 httpDownloadProbabilitySkipIfNoPeers: true,
265
266 p2pDownloadMaxPriority: 50
267 }
268 258
269 return { 259 return {
270 trackerAnnounce, 260 trackerAnnounce,
@@ -283,13 +273,57 @@ export class PeertubePlayerOptionsBuilder {
283 } 273 }
284 } 274 }
285 275
276 private getP2PMediaLoaderLiveOptions (): Partial<HybridLoaderSettings> {
277 const base = {
278 requiredSegmentsPriority: 1
279 }
280
281 const latencyMode = this.options.common.liveOptions.latencyMode
282
283 switch (latencyMode) {
284 case LiveVideoLatencyMode.SMALL_LATENCY:
285 return {
286 ...base,
287
288 useP2P: false,
289 httpDownloadProbability: 1
290 }
291
292 case LiveVideoLatencyMode.HIGH_LATENCY:
293 return base
294
295 default:
296 return base
297 }
298 }
299
300 private getP2PMediaLoaderVODOptions (): Partial<HybridLoaderSettings> {
301 return {
302 requiredSegmentsPriority: 3,
303
304 cachedSegmentExpiration: 86400000,
305 cachedSegmentsCount: 100,
306
307 httpDownloadMaxPriority: 9,
308 httpDownloadProbability: 0.06,
309 httpDownloadProbabilitySkipIfNoPeers: true,
310
311 p2pDownloadMaxPriority: 50
312 }
313 }
314
286 private getHLSOptions (p2pMediaLoaderConfig: HlsJsEngineSettings) { 315 private getHLSOptions (p2pMediaLoaderConfig: HlsJsEngineSettings) {
316 const specificLiveOrVODOptions = this.options.common.isLive
317 ? this.getHLSLiveOptions()
318 : this.getHLSVODOptions()
319
287 const base = { 320 const base = {
288 capLevelToPlayerSize: true, 321 capLevelToPlayerSize: true,
289 autoStartLoad: false, 322 autoStartLoad: false,
290 liveSyncDurationCount: 5,
291 323
292 loader: new this.p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() 324 loader: new this.p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass(),
325
326 ...specificLiveOrVODOptions
293 } 327 }
294 328
295 const averageBandwidth = getAverageBandwidthInStore() 329 const averageBandwidth = getAverageBandwidthInStore()
@@ -305,6 +339,33 @@ export class PeertubePlayerOptionsBuilder {
305 } 339 }
306 } 340 }
307 341
342 private getHLSLiveOptions () {
343 const latencyMode = this.options.common.liveOptions.latencyMode
344
345 switch (latencyMode) {
346 case LiveVideoLatencyMode.SMALL_LATENCY:
347 return {
348 liveSyncDurationCount: 2
349 }
350
351 case LiveVideoLatencyMode.HIGH_LATENCY:
352 return {
353 liveSyncDurationCount: 10
354 }
355
356 default:
357 return {
358 liveSyncDurationCount: 5
359 }
360 }
361 }
362
363 private getHLSVODOptions () {
364 return {
365 liveSyncDurationCount: 5
366 }
367 }
368
308 private addWebTorrentOptions (plugins: VideoJSPluginOptions, alreadyPlayed: boolean) { 369 private addWebTorrentOptions (plugins: VideoJSPluginOptions, alreadyPlayed: boolean) {
309 const commonOptions = this.options.common 370 const commonOptions = this.options.common
310 const webtorrentOptions = this.options.webtorrent 371 const webtorrentOptions = this.options.webtorrent
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 38ff39890..9e4d87911 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -6,6 +6,7 @@ import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
6import { 6import {
7 HTMLServerConfig, 7 HTMLServerConfig,
8 HttpStatusCode, 8 HttpStatusCode,
9 LiveVideo,
9 OAuth2ErrorCode, 10 OAuth2ErrorCode,
10 ResultList, 11 ResultList,
11 UserRefreshToken, 12 UserRefreshToken,
@@ -94,6 +95,10 @@ export class PeerTubeEmbed {
94 return window.location.origin + '/api/v1/videos/' + id 95 return window.location.origin + '/api/v1/videos/' + id
95 } 96 }
96 97
98 getLiveUrl (videoId: string) {
99 return window.location.origin + '/api/v1/videos/live/' + videoId
100 }
101
97 refreshFetch (url: string, options?: RequestInit) { 102 refreshFetch (url: string, options?: RequestInit) {
98 return fetch(url, options) 103 return fetch(url, options)
99 .then((res: Response) => { 104 .then((res: Response) => {
@@ -166,6 +171,12 @@ export class PeerTubeEmbed {
166 return this.refreshFetch(this.getVideoUrl(videoId) + '/captions', { headers: this.headers }) 171 return this.refreshFetch(this.getVideoUrl(videoId) + '/captions', { headers: this.headers })
167 } 172 }
168 173
174 loadWithLive (video: VideoDetails) {
175 return this.refreshFetch(this.getLiveUrl(video.uuid), { headers: this.headers })
176 .then(res => res.json())
177 .then((live: LiveVideo) => ({ video, live }))
178 }
179
169 loadPlaylistInfo (playlistId: string): Promise<Response> { 180 loadPlaylistInfo (playlistId: string): Promise<Response> {
170 return this.refreshFetch(this.getPlaylistUrl(playlistId), { headers: this.headers }) 181 return this.refreshFetch(this.getPlaylistUrl(playlistId), { headers: this.headers })
171 } 182 }
@@ -475,13 +486,15 @@ export class PeerTubeEmbed {
475 .then(res => res.json()) 486 .then(res => res.json())
476 } 487 }
477 488
478 const videoInfoPromise = videoResponse.json() 489 const videoInfoPromise: Promise<{ video: VideoDetails, live?: LiveVideo }> = videoResponse.json()
479 .then((videoInfo: VideoDetails) => { 490 .then((videoInfo: VideoDetails) => {
480 this.loadParams(videoInfo) 491 this.loadParams(videoInfo)
481 492
482 if (!alreadyHadPlayer && !this.autoplay) this.loadPlaceholder(videoInfo) 493 if (!alreadyHadPlayer && !this.autoplay) this.buildPlaceholder(videoInfo)
483 494
484 return videoInfo 495 if (!videoInfo.isLive) return { video: videoInfo }
496
497 return this.loadWithLive(videoInfo)
485 }) 498 })
486 499
487 const [ videoInfoTmp, serverTranslations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([ 500 const [ videoInfoTmp, serverTranslations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([
@@ -493,11 +506,15 @@ export class PeerTubeEmbed {
493 506
494 await this.loadPlugins(serverTranslations) 507 await this.loadPlugins(serverTranslations)
495 508
496 const videoInfo: VideoDetails = videoInfoTmp 509 const { video: videoInfo, live } = videoInfoTmp
497 510
498 const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager 511 const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
499 const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse) 512 const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
500 513
514 const liveOptions = videoInfo.isLive
515 ? { latencyMode: live.latencyMode }
516 : undefined
517
501 const playlistPlugin = this.currentPlaylistElement 518 const playlistPlugin = this.currentPlaylistElement
502 ? { 519 ? {
503 elements: this.playlistElements, 520 elements: this.playlistElements,
@@ -545,6 +562,7 @@ export class PeerTubeEmbed {
545 videoUUID: videoInfo.uuid, 562 videoUUID: videoInfo.uuid,
546 563
547 isLive: videoInfo.isLive, 564 isLive: videoInfo.isLive,
565 liveOptions,
548 566
549 playerElement: this.playerElement, 567 playerElement: this.playerElement,
550 onPlayerElementChange: (element: HTMLVideoElement) => { 568 onPlayerElementChange: (element: HTMLVideoElement) => {
@@ -726,7 +744,7 @@ export class PeerTubeEmbed {
726 return [] 744 return []
727 } 745 }
728 746
729 private loadPlaceholder (video: VideoDetails) { 747 private buildPlaceholder (video: VideoDetails) {
730 const placeholder = this.getPlaceholderElement() 748 const placeholder = this.getPlaceholderElement()
731 749
732 const url = window.location.origin + video.previewPath 750 const url = window.location.origin + video.previewPath