X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2F%2Bvideos%2F%2Bvideo-edit%2Fvideo-add-components%2Fvideo-upload.component.ts;h=28d7ec45894852cc37fccb837ee9822bd41499df;hb=9ea02c48a7a47b5bbed261e847d7d671e266a073;hp=e20f0887929e0648e33a180a7fda7636230df90f;hpb=a1eda903a497857017495f37a1fd3593ba7ab23c;p=github%2FChocobozzz%2FPeerTube.git diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts index e20f08879..28d7ec458 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts @@ -1,16 +1,17 @@ +import { truncate } from 'lodash-es' +import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx' +import { isIOS } from 'src/assets/player/utils' +import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http' import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' import { Router } from '@angular/router' -import { UploadxOptions, UploadState, UploadxService } from 'ngx-uploadx' -import { UploaderXFormData } from './uploaderx-form-data' import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService, UserService } from '@app/core' -import { scrollToTop, genericUploadErrorHandler } from '@app/helpers' +import { genericUploadErrorHandler, scrollToTop } from '@app/helpers' import { FormValidatorService } from '@app/shared/shared-forms' -import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' +import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { LoadingBarService } from '@ngx-loading-bar/core' -import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' -import { VideoPrivacy } from '@shared/models' +import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models' +import { UploaderXFormData } from './uploaderx-form-data' import { VideoSend } from './video-send' -import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http' @Component({ selector: 'my-video-upload', @@ -34,9 +35,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy videoUploaded = false videoUploadPercents = 0 - videoUploadedIds = { + videoUploadedIds: VideoCreateResult = { id: 0, - uuid: '' + uuid: '', + shortUUID: '' } formData: FormData @@ -45,9 +47,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy error: string enableRetryAfterError: boolean + schedulePublicationPossible = false + // So that it can be accessed in the template - protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC - protected readonly BASE_VIDEO_UPLOAD_URL = VideoService.BASE_VIDEO_URL + 'upload-resumable' + protected readonly BASE_VIDEO_UPLOAD_URL = VideoService.BASE_VIDEO_URL + '/upload-resumable' private uploadxOptions: UploadxOptions private isUpdatingVideo = false @@ -68,15 +71,22 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy ) { super() + // FIXME: https://github.com/Chocobozzz/PeerTube/issues/4382#issuecomment-915854167 + const chunkSize = isIOS() + ? 0 + : undefined // Auto chunk size + this.uploadxOptions = { endpoint: this.BASE_VIDEO_UPLOAD_URL, multiple: false, token: this.authService.getAccessToken(), uploaderClass: UploaderXFormData, + chunkSize, retryConfig: { - maxAttempts: 6, - shouldRetry: (code: number) => { - return code < 400 || code >= 501 + maxAttempts: 30, // maximum attempts for 503 codes, otherwise set to 6, see below + maxDelay: 120_000, // 2 min + shouldRetry: (code: number, attempts: number) => { + return code === HttpStatusCode.SERVICE_UNAVAILABLE_503 || ((code < 400 || code > 500) && attempts < 6) } } } @@ -86,9 +96,49 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy return this.serverConfig.video.file.extensions.join(', ') } + ngOnInit () { + super.ngOnInit() + + this.userService.getMyVideoQuotaUsed() + .subscribe(data => { + this.userVideoQuotaUsed = data.videoQuotaUsed + this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily + }) + + this.resumableUploadService.events + .subscribe(state => this.onUploadVideoOngoing(state)) + + this.schedulePublicationPossible = this.videoPrivacies.some(p => p.id === VideoPrivacy.PRIVATE) + } + + ngAfterViewInit () { + this.hooks.runAction('action:video-upload.init', 'video-edit') + } + + ngOnDestroy () { + this.cancelUpload() + } + + canDeactivate () { + let text = '' + + if (this.videoUploaded === true) { + // We can't concatenate strings using $localize + text = $localize`Your video was uploaded to your account and is private.` + ' ' + + $localize`But associated data (tags, description...) will be lost, are you sure you want to leave this page?` + } else { + text = $localize`Your video is not uploaded yet, are you sure you want to leave this page?` + } + + return { + canDeactivate: !this.isUploadingVideo, + text + } + } + onUploadVideoOngoing (state: UploadState) { switch (state.status) { - case 'error': + case 'error': { const error = state.response?.error || 'Unknow error' this.handleUploadError({ @@ -103,6 +153,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy url: state.url }) break + } case 'cancelled': this.isUploadingVideo = false @@ -111,6 +162,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy this.firstStepError.emit() this.enableRetryAfterError = false this.error = '' + this.isUploadingAudioFile = false break case 'queue': @@ -134,44 +186,6 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } } - ngOnInit () { - super.ngOnInit() - - this.userService.getMyVideoQuotaUsed() - .subscribe(data => { - this.userVideoQuotaUsed = data.videoQuotaUsed - this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily - }) - - this.resumableUploadService.events - .subscribe(state => this.onUploadVideoOngoing(state)) - } - - ngAfterViewInit () { - this.hooks.runAction('action:video-upload.init', 'video-edit') - } - - ngOnDestroy () { - this.cancelUpload() - } - - canDeactivate () { - let text = '' - - if (this.videoUploaded === true) { - // FIXME: cannot concatenate strings using $localize - text = $localize`Your video was uploaded to your account and is private.` + ' ' + - $localize`But associated data (tags, description...) will be lost, are you sure you want to leave this page?` - } else { - text = $localize`Your video is not uploaded yet, are you sure you want to leave this page?` - } - - return { - canDeactivate: !this.isUploadingVideo, - text - } - } - onFileDropped (files: FileList) { this.videofileInput.nativeElement.files = files @@ -234,25 +248,26 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy video.patch(this.form.value) video.id = this.videoUploadedIds.id video.uuid = this.videoUploadedIds.uuid + video.shortUUID = this.videoUploadedIds.shortUUID this.isUpdatingVideo = true this.updateVideoAndCaptions(video) - .subscribe( - () => { + .subscribe({ + next: () => { this.isUpdatingVideo = false this.isUploadingVideo = false this.notifier.success($localize`Video published.`) - this.router.navigate([ '/w', video.uuid ]) + this.router.navigateByUrl(Video.buildWatchUrl(video)) }, - err => { + error: err => { this.error = err.message scrollToTop() console.error(err) } - ) + }) } private getInputVideoFile () { @@ -266,7 +281,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy downloadEnabled: true, channelId: this.firstStepChannelId, nsfw: this.serverConfig.instance.isNSFW, - privacy: VideoPrivacy.PRIVATE.toString(), + privacy: this.highestPrivacy.toString(), + name: this.buildVideoFilename(file.name), filename: file.name, previewfile: previewfile as any } @@ -297,8 +313,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } private closeFirstStep (filename: string) { - const nameWithoutExtension = filename.replace(/\.[^/.]+$/, '') - const name = nameWithoutExtension.length < 3 ? filename : nameWithoutExtension + const name = this.buildVideoFilename(filename) this.form.patchValue({ name, @@ -321,6 +336,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy const videoQuotaUsedBytes = bytePipes.transform(this.userVideoQuotaUsed, 0) const videoQuotaBytes = bytePipes.transform(videoQuota, 0) + // eslint-disable-next-line max-len const msg = $localize`Your video quota is exceeded with this video (video size: ${videoSizeBytes}, used: ${videoQuotaUsedBytes}, quota: ${videoQuotaBytes})` this.notifier.error(msg) @@ -339,6 +355,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy const videoSizeBytes = bytePipes.transform(videofile.size, 0) const quotaUsedDailyBytes = bytePipes.transform(this.userVideoQuotaUsedDaily, 0) const quotaDailyBytes = bytePipes.transform(videoQuotaDaily, 0) + // eslint-disable-next-line max-len const msg = $localize`Your daily video quota is exceeded with this video (video size: ${videoSizeBytes}, used: ${quotaUsedDailyBytes}, quota: ${quotaDailyBytes})` this.notifier.error(msg) @@ -353,4 +370,18 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy return extensions.some(e => filename.endsWith(e)) } + + private buildVideoFilename (filename: string) { + const nameWithoutExtension = filename.replace(/\.[^/.]+$/, '') + let name = nameWithoutExtension.length < 3 + ? filename + : nameWithoutExtension + + const videoNameMaxSize = 110 + if (name.length > videoNameMaxSize) { + name = truncate(name, { length: videoNameMaxSize, omission: '' }) + } + + return name + } }