2021-03-24 09:58:36 +00:00
import { AfterViewInit , Component , ElementRef , EventEmitter , OnDestroy , OnInit , Output , ViewChild } from '@angular/core'
2018-08-02 13:34:09 +00:00
import { Router } from '@angular/router'
2021-05-10 09:13:41 +00:00
import { UploadxOptions , UploadState , UploadxService } from 'ngx-uploadx'
import { UploaderXFormData } from './uploaderx-form-data'
2021-03-24 09:58:36 +00:00
import { AuthService , CanComponentDeactivate , HooksService , Notifier , ServerService , UserService } from '@app/core'
2021-05-10 09:13:41 +00:00
import { scrollToTop , genericUploadErrorHandler } from '@app/helpers'
2020-06-23 12:10:17 +00:00
import { FormValidatorService } from '@app/shared/shared-forms'
2020-08-11 14:50:00 +00:00
import { BytesPipe , VideoCaptionService , VideoEdit , VideoService } from '@app/shared/shared-main'
2018-08-02 13:34:09 +00:00
import { LoadingBarService } from '@ngx-loading-bar/core'
2021-03-24 09:58:36 +00:00
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
2020-06-23 12:10:17 +00:00
import { VideoPrivacy } from '@shared/models'
2020-06-23 12:49:20 +00:00
import { VideoSend } from './video-send'
2021-05-10 09:13:41 +00:00
import { HttpErrorResponse , HttpEventType , HttpHeaders } from '@angular/common/http'
2018-08-02 13:34:09 +00:00
@Component ( {
selector : 'my-video-upload' ,
templateUrl : './video-upload.component.html' ,
styleUrls : [
2018-08-06 13:30:24 +00:00
'../shared/video-edit.component.scss' ,
2019-01-16 15:05:40 +00:00
'./video-upload.component.scss' ,
'./video-send.scss'
2018-08-02 13:34:09 +00:00
]
} )
2021-05-10 09:13:41 +00:00
export class VideoUploadComponent extends VideoSend implements OnInit , OnDestroy , AfterViewInit , CanComponentDeactivate {
2018-08-02 13:34:09 +00:00
@Output ( ) firstStepDone = new EventEmitter < string > ( )
2018-11-16 09:05:25 +00:00
@Output ( ) firstStepError = new EventEmitter < void > ( )
2020-02-07 09:00:34 +00:00
@ViewChild ( 'videofileInput' ) videofileInput : ElementRef < HTMLInputElement >
2018-08-02 13:34:09 +00:00
2018-08-06 13:12:54 +00:00
userVideoQuotaUsed = 0
2018-08-28 07:01:35 +00:00
userVideoQuotaUsedDaily = 0
2018-08-06 13:12:54 +00:00
2019-05-17 08:45:53 +00:00
isUploadingAudioFile = false
2018-08-02 13:34:09 +00:00
isUploadingVideo = false
2019-05-17 08:45:53 +00:00
2018-08-02 13:34:09 +00:00
videoUploaded = false
videoUploadPercents = 0
videoUploadedIds = {
id : 0 ,
uuid : ''
}
2020-11-23 09:45:42 +00:00
formData : FormData
2019-05-17 08:45:53 +00:00
previewfileUpload : File
2018-08-02 13:34:09 +00:00
2018-11-16 09:05:25 +00:00
error : string
2020-11-23 09:45:42 +00:00
enableRetryAfterError : boolean
2018-08-02 13:34:09 +00:00
2021-05-10 09:13:41 +00:00
// So that it can be accessed in the template
2018-08-06 13:12:54 +00:00
protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy . PUBLIC
2021-05-10 09:13:41 +00:00
protected readonly BASE_VIDEO_UPLOAD_URL = VideoService . BASE_VIDEO_URL + 'upload-resumable'
private uploadxOptions : UploadxOptions
private isUpdatingVideo = false
private fileToUpload : File
2018-08-02 13:34:09 +00:00
constructor (
protected formValidatorService : FormValidatorService ,
2018-08-06 13:12:54 +00:00
protected loadingBar : LoadingBarService ,
2018-12-19 15:04:34 +00:00
protected notifier : Notifier ,
2018-08-06 13:12:54 +00:00
protected authService : AuthService ,
protected serverService : ServerService ,
protected videoService : VideoService ,
protected videoCaptionService : VideoCaptionService ,
2018-08-02 13:34:09 +00:00
private userService : UserService ,
2021-03-24 09:58:36 +00:00
private router : Router ,
2021-05-10 09:13:41 +00:00
private hooks : HooksService ,
private resumableUploadService : UploadxService
) {
2018-08-02 13:34:09 +00:00
super ( )
2021-05-10 09:13:41 +00:00
this . uploadxOptions = {
endpoint : this.BASE_VIDEO_UPLOAD_URL ,
multiple : false ,
token : this.authService.getAccessToken ( ) ,
uploaderClass : UploaderXFormData ,
retryConfig : {
maxAttempts : 6 ,
shouldRetry : ( code : number ) = > {
return code < 400 || code >= 501
}
}
}
2018-08-02 13:34:09 +00:00
}
get videoExtensions ( ) {
2020-03-06 23:15:49 +00:00
return this . serverConfig . video . file . extensions . join ( ', ' )
2018-08-02 13:34:09 +00:00
}
2021-05-10 09:13:41 +00:00
onUploadVideoOngoing ( state : UploadState ) {
switch ( state . status ) {
case 'error' :
const error = state . response ? . error || 'Unknow error'
this . handleUploadError ( {
error : new Error ( error ) ,
name : 'HttpErrorResponse' ,
message : error ,
ok : false ,
headers : new HttpHeaders ( state . responseHeaders ) ,
status : + state . responseStatus ,
statusText : error ,
type : HttpEventType . Response ,
url : state.url
} )
break
case 'cancelled' :
this . isUploadingVideo = false
this . videoUploadPercents = 0
this . firstStepError . emit ( )
this . enableRetryAfterError = false
this . error = ''
break
case 'queue' :
this . closeFirstStep ( state . name )
break
case 'uploading' :
this . videoUploadPercents = state . progress
break
case 'paused' :
this . notifier . info ( $localize ` Upload cancelled ` )
break
case 'complete' :
this . videoUploaded = true
this . videoUploadPercents = 100
this . videoUploadedIds = state ? . response . video
break
}
}
2018-08-02 13:34:09 +00:00
ngOnInit ( ) {
2018-08-06 13:12:54 +00:00
super . ngOnInit ( )
2018-08-02 13:34:09 +00:00
this . userService . getMyVideoQuotaUsed ( )
2018-09-03 07:30:13 +00:00
. subscribe ( data = > {
this . userVideoQuotaUsed = data . videoQuotaUsed
this . userVideoQuotaUsedDaily = data . videoQuotaUsedDaily
} )
2021-05-10 09:13:41 +00:00
this . resumableUploadService . events
. subscribe ( state = > this . onUploadVideoOngoing ( state ) )
2018-08-02 13:34:09 +00:00
}
2021-03-24 09:58:36 +00:00
ngAfterViewInit ( ) {
this . hooks . runAction ( 'action:video-upload.init' , 'video-edit' )
}
2018-08-02 13:34:09 +00:00
ngOnDestroy ( ) {
2021-05-10 09:13:41 +00:00
this . cancelUpload ( )
2018-08-02 13:34:09 +00:00
}
canDeactivate ( ) {
let text = ''
if ( this . videoUploaded === true ) {
2020-12-03 09:39:43 +00:00
// FIXME: cannot concatenate strings using $localize
2020-08-12 08:40:04 +00:00
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? `
2018-08-02 13:34:09 +00:00
} else {
2020-08-12 08:40:04 +00:00
text = $localize ` Your video is not uploaded yet, are you sure you want to leave this page? `
2018-08-02 13:34:09 +00:00
}
return {
canDeactivate : ! this . isUploadingVideo ,
text
}
}
2021-05-10 09:13:41 +00:00
onFileDropped ( files : FileList ) {
2020-04-02 22:22:04 +00:00
this . videofileInput . nativeElement . files = files
2019-05-17 08:45:53 +00:00
2021-05-10 09:13:41 +00:00
this . onFileChange ( { target : this.videofileInput.nativeElement } )
2019-05-17 08:45:53 +00:00
}
2021-05-10 09:13:41 +00:00
onFileChange ( event : Event | { target : HTMLInputElement } ) {
const file = ( event . target as HTMLInputElement ) . files [ 0 ]
2020-04-14 07:54:22 +00:00
2021-05-10 09:13:41 +00:00
if ( ! file ) return
2020-04-14 07:54:22 +00:00
2021-05-10 09:13:41 +00:00
if ( ! this . checkGlobalUserQuota ( file ) ) return
if ( ! this . checkDailyUserQuota ( file ) ) return
2018-08-02 13:34:09 +00:00
2021-05-10 09:13:41 +00:00
if ( this . isAudioFile ( file . name ) ) {
2019-05-17 08:45:53 +00:00
this . isUploadingAudioFile = true
2018-08-28 07:01:35 +00:00
return
}
2018-08-02 13:34:09 +00:00
this . isUploadingVideo = true
2021-05-10 09:13:41 +00:00
this . fileToUpload = file
2018-08-02 13:34:09 +00:00
2021-05-10 09:13:41 +00:00
this . uploadFile ( file )
2020-11-23 09:45:42 +00:00
}
2021-05-10 09:13:41 +00:00
uploadAudio ( ) {
this . uploadFile ( this . getInputVideoFile ( ) , this . previewfileUpload )
}
2018-08-02 13:34:09 +00:00
2021-05-10 09:13:41 +00:00
retryUpload ( ) {
this . enableRetryAfterError = false
this . error = ''
this . uploadFile ( this . fileToUpload )
}
2020-12-08 20:16:10 +00:00
2021-05-10 09:13:41 +00:00
cancelUpload ( ) {
this . resumableUploadService . control ( { action : 'cancel' } )
2018-08-02 13:34:09 +00:00
}
2018-10-01 07:04:43 +00:00
isPublishingButtonDisabled ( ) {
return ! this . form . valid ||
this . isUpdatingVideo === true ||
2020-08-03 14:16:58 +00:00
this . videoUploaded !== true ||
! this . videoUploadedIds . id
2018-10-01 07:04:43 +00:00
}
2021-05-10 09:13:41 +00:00
getAudioUploadLabel ( ) {
const videofile = this . getInputVideoFile ( )
if ( ! videofile ) return $localize ` Upload `
return $localize ` Upload ${ videofile . name } `
}
2018-08-02 13:34:09 +00:00
updateSecondStep ( ) {
2020-08-03 14:16:58 +00:00
if ( this . isPublishingButtonDisabled ( ) || ! this . checkForm ( ) ) {
2018-08-02 13:34:09 +00:00
return
}
const video = new VideoEdit ( )
video . patch ( this . form . value )
video . id = this . videoUploadedIds . id
video . uuid = this . videoUploadedIds . uuid
this . isUpdatingVideo = true
2018-08-06 13:12:54 +00:00
this . updateVideoAndCaptions ( video )
2018-08-02 13:34:09 +00:00
. subscribe (
( ) = > {
this . isUpdatingVideo = false
this . isUploadingVideo = false
2020-08-12 08:40:04 +00:00
this . notifier . success ( $localize ` Video published. ` )
2018-08-02 13:34:09 +00:00
this . router . navigate ( [ '/videos/watch' , video . uuid ] )
} ,
err = > {
2018-11-16 09:05:25 +00:00
this . error = err . message
scrollToTop ( )
2018-08-02 13:34:09 +00:00
console . error ( err )
}
)
}
2019-05-17 08:45:53 +00:00
2021-05-10 09:13:41 +00:00
private getInputVideoFile ( ) {
return this . videofileInput . nativeElement . files [ 0 ]
}
private uploadFile ( file : File , previewfile? : File ) {
const metadata = {
waitTranscoding : true ,
commentsEnabled : true ,
downloadEnabled : true ,
channelId : this.firstStepChannelId ,
nsfw : this.serverConfig.instance.isNSFW ,
privacy : VideoPrivacy.PRIVATE.toString ( ) ,
filename : file.name ,
previewfile : previewfile as any
}
this . resumableUploadService . handleFiles ( file , {
. . . this . uploadxOptions ,
metadata
} )
this . isUploadingVideo = true
}
private handleUploadError ( err : HttpErrorResponse ) {
// Reset progress (but keep isUploadingVideo true)
this . videoUploadPercents = 0
this . enableRetryAfterError = true
this . error = genericUploadErrorHandler ( {
err ,
name : $localize ` video ` ,
notifier : this.notifier ,
sticky : false
} )
if ( err . status === HttpStatusCode . UNSUPPORTED_MEDIA_TYPE_415 ) {
this . cancelUpload ( )
}
}
private closeFirstStep ( filename : string ) {
const nameWithoutExtension = filename . replace ( /\.[^/.]+$/ , '' )
const name = nameWithoutExtension . length < 3 ? filename : nameWithoutExtension
this . form . patchValue ( {
name ,
privacy : this.firstStepPrivacyId ,
nsfw : this.serverConfig.instance.isNSFW ,
channelId : this.firstStepChannelId ,
previewfile : this.previewfileUpload
} )
this . firstStepDone . emit ( name )
}
2019-05-17 08:45:53 +00:00
private checkGlobalUserQuota ( videofile : File ) {
const bytePipes = new BytesPipe ( )
// Check global user quota
const videoQuota = this . authService . getUser ( ) . videoQuota
if ( videoQuota !== - 1 && ( this . userVideoQuotaUsed + videofile . size ) > videoQuota ) {
2020-08-12 08:40:04 +00:00
const videoSizeBytes = bytePipes . transform ( videofile . size , 0 )
const videoQuotaUsedBytes = bytePipes . transform ( this . userVideoQuotaUsed , 0 )
const videoQuotaBytes = bytePipes . transform ( videoQuota , 0 )
2021-05-10 09:13:41 +00:00
const msg = $localize ` Your video quota is exceeded with this video (video size: ${ videoSizeBytes } , used: ${ videoQuotaUsedBytes } , quota: ${ videoQuotaBytes } ) `
2019-05-17 08:45:53 +00:00
this . notifier . error ( msg )
return false
}
return true
}
private checkDailyUserQuota ( videofile : File ) {
const bytePipes = new BytesPipe ( )
// Check daily user quota
const videoQuotaDaily = this . authService . getUser ( ) . videoQuotaDaily
if ( videoQuotaDaily !== - 1 && ( this . userVideoQuotaUsedDaily + videofile . size ) > videoQuotaDaily ) {
2020-08-12 08:40:04 +00:00
const videoSizeBytes = bytePipes . transform ( videofile . size , 0 )
const quotaUsedDailyBytes = bytePipes . transform ( this . userVideoQuotaUsedDaily , 0 )
const quotaDailyBytes = bytePipes . transform ( videoQuotaDaily , 0 )
2021-05-10 09:13:41 +00:00
const msg = $localize ` Your daily video quota is exceeded with this video (video size: ${ videoSizeBytes } , used: ${ quotaUsedDailyBytes } , quota: ${ quotaDailyBytes } ) `
2019-05-17 08:45:53 +00:00
this . notifier . error ( msg )
return false
}
return true
}
private isAudioFile ( filename : string ) {
2020-02-07 07:51:28 +00:00
const extensions = [ '.mp3' , '.flac' , '.ogg' , '.wma' , '.wav' ]
return extensions . some ( e = > filename . endsWith ( e ) )
2019-05-17 08:45:53 +00:00
}
2018-08-02 13:34:09 +00:00
}