+import { truncate } from 'lodash-es'
import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
+import { isIOS } from '@root-helpers/web-browser'
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 { FormValidatorService } from '@app/shared/shared-forms'
import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
-import { HttpStatusCode, VideoPrivacy } from '@shared/models'
+import { HttpStatusCode, VideoCreateResult } from '@shared/models'
import { UploaderXFormData } from './uploaderx-form-data'
import { VideoSend } from './video-send'
videoUploaded = false
videoUploadPercents = 0
- videoUploadedIds = {
+ videoUploadedIds: VideoCreateResult = {
id: 0,
- uuid: ''
+ uuid: '',
+ shortUUID: ''
}
formData: FormData
enableRetryAfterError: boolean
// So that it can be accessed in the template
- 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
) {
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)
}
}
}
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))
+ }
+
+ 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({
url: state.url
})
break
+ }
case 'cancelled':
this.isUploadingVideo = false
this.firstStepError.emit()
this.enableRetryAfterError = false
this.error = ''
+ this.isUploadingAudioFile = false
break
case 'queue':
}
}
- 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
return $localize`Upload ${videofile.name}`
}
- updateSecondStep () {
- if (this.isPublishingButtonDisabled() || !this.checkForm()) {
- return
- }
+ async updateSecondStep () {
+ if (!await this.isFormValid()) return
+ if (this.isPublishingButtonDisabled()) return
const video = new VideoEdit()
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.router.navigateByUrl(Video.buildWatchUrl(video))
},
- err => {
+ error: err => {
this.error = err.message
scrollToTop()
console.error(err)
}
- )
+ })
}
private getInputVideoFile () {
private uploadFile (file: File, previewfile?: File) {
const metadata = {
waitTranscoding: true,
- commentsEnabled: true,
- downloadEnabled: true,
channelId: this.firstStepChannelId,
nsfw: this.serverConfig.instance.isNSFW,
privacy: this.highestPrivacy.toString(),
+ name: this.buildVideoFilename(file.name),
filename: file.name,
previewfile: previewfile as any
}
}
private closeFirstStep (filename: string) {
- const nameWithoutExtension = filename.replace(/\.[^/.]+$/, '')
- const name = nameWithoutExtension.length < 3 ? filename : nameWithoutExtension
+ const name = this.buildVideoFilename(filename)
this.form.patchValue({
name,
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)
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)
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
+ }
}