{"version":3,"file":"7311.eb3db2283be103db.js","mappings":"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","names":["PAGE","ClipsService","_ClipsService","constructor","store","sentryService","clipsApi","youtubeService","analyticsService","this","getPoster","clip","res","poster","DEFAULT_POSTER","youtube_id","getThumbnail","url","createClipFromFilestack","filestackResult","projectId","userId","_this","_asyncToGenerator","uploadTags","lastModifiedDate","duration","environment","production","console","log","warn","Error","newVideo","filename","type","mimetype","size","Date","toISOString","loading","originalFile","lastModified","convertVideoFileToClip","source","src","filestackConfig","storeTo","path","orig","key","name","file","id","Utils","removeFileExt","filmingDate","getDateTimeString","createClip","then","newClip","subClipsUpdated","dispatch","mystackActions","toFixed","seconds","durationInSeconds","clipUpload","error","DEBUG_LOGS","hlsMeta","HLS_META_TRANSCODE","Clip","clipActions","catch","err","Array","isArray","errors","length","errorType","info","message","params","sub","subscribeClipsUpdated","subscribe","next","updates","JSON","parse","hlsSrc","sources","Object","keys","isHlsComplete","unsubscribe","captureError","errorMessage","complete","getPosterTimeFromPoster","sNum","substring","lastIndexOf","parsed","parseInt","updatePosterToPosterTime","posterTime","newPoster","slice","startIndex","endIndex","handleSubClips","i0","i1","i2","i3","i4","i5","factory","ɵfac","providedIn","ClipUploadDirective","_ClipUploadDirective","BaseFileUpload","filestackService","translate","platform","super","allowedFileTypes","customPickerOptions","maxSize","MAX_VIDEO_CAPTURE_SIZE_MB","maxFiles","accept","container","region","uploadConfig","tags","fromSources","onClick","event","openFilePicker","makeFileUploadName","makeCaptureClipFilename","onFileSelected","Promise","resolve","reject","instant","includes","not_supported_file_type","supported_file_types","join","checkVideoLength","durationOK","durationMessage","minutes","MAX_VIDEO_CAPTURE_LENGTH_MINUTES","originalName","filelastModified","FilelastModified","now","pinpointEvent","platforms","toString","lastModifiedDateISO","filesize","video","document","createElement","preload","checkDuration","maxCaptureLength","MAX_VIDEO_CAPTURE_LENGTH_SECONDS","removeEventListener","window","URL","webkitURL","revokeObjectURL","addEventListener","captureMessage","createObjectURL","selectors","hostBindings","rf","ctx","$event"],"ignoreList":[],"sourceRoot":"webpack:///","sources":["./src/app/clips/shared/services/clips.service.ts","./src/app/core/filestack/directives/clip-upload.directive.ts"],"sourcesContent":["/** @format */\n\nimport { Injectable } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { State } from '@store/reducers';\nimport * as clipActions from '@store/actions/clips.actions';\nimport * as mystackActions from '@store/actions/mystack.actions';\nimport { ClipsApiService } from '@app/core/api/clips-api.service';\nimport { AnalyticsService } from '@app/core/services/analytics/analytics.service';\nimport { SentryService } from '@app/core/services/sentry.service';\nimport { YoutubeService } from '@app/core/services/youtube.service';\nimport { Utils } from '@app/shared/utils';\nimport { ClipVideoFile, Clip } from '@app/shared/models/clip.model';\nimport { convertVideoFileToClip, isHlsComplete } from '@app/shared/models/clip.model';\nimport { HLS_META_TRANSCODE, DEFAULT_POSTER } from '@app/shared/models/clip.model';\nimport { environment, filestackConfig } from 'src/environments/environment';\n\nconst DEBUG_LOGS = false;\nconst PAGE = '[ClipsService]';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ClipsService {\n  constructor(\n    private store: Store<State>,\n    private sentryService: SentryService,\n    private clipsApi: ClipsApiService,\n    private youtubeService: YoutubeService,\n    private analyticsService: AnalyticsService\n  ) {}\n\n  getPoster(clip: Clip, res: string = 'default') {\n    let poster = DEFAULT_POSTER;\n    if (clip) {\n      if (clip.poster) {\n        poster = clip.poster;\n      } else if (clip.youtube_id) {\n        poster = this.youtubeService.getThumbnail(clip.youtube_id, res).url;\n      }\n    }\n    return poster;\n  }\n\n  /**\n   * Create a new Clip from the FileStack Upload\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  async createClipFromFilestack(filestackResult: any, projectId: string, userId: string) {\n    DEBUG_LOGS && console.log(`createClipFromFilestack filestackResult =`, filestackResult);\n    const {\n      uploadTags: {\n        lastModifiedDate = null, // = (new Date()).toISOString(),\n        // filesize = filestackResult.size, // don't use the stringified from uploadTags\n        duration = 0,\n      } = {},\n    } = filestackResult;\n    // MVP-1169 file selected metadata\n    !environment.production &&\n      console.log(`get size & updatedDate from filestackResult:`, { lastModifiedDate, duration, filestackResult });\n\n    if (!filestackResult || !projectId) {\n      console.warn(`${PAGE} createClipFromFilestack missing required params`, { filestackResult, projectId });\n      throw new Error('Missing required parameters - unable to create clip.');\n    }\n\n    const newVideo: ClipVideoFile = {\n      filename: filestackResult.filename,\n      type: filestackResult.mimetype,\n      size: filestackResult.size,\n      lastModifiedDate: new Date().toISOString(),\n      loading: true,\n    };\n\n    // get additional metadata if it exists.\n    if (lastModifiedDate) {\n      newVideo.lastModifiedDate = lastModifiedDate;\n    } else if (filestackResult.originalFile) {\n      if (\n        filestackResult.originalFile.lastModifiedDate &&\n        typeof filestackResult.originalFile.lastModifiedDate.toISOString === 'function'\n      ) {\n        newVideo.lastModifiedDate = filestackResult.originalFile.lastModifiedDate.toISOString();\n      } else if (filestackResult.originalFile.lastModified) {\n        newVideo.lastModifiedDate = new Date(filestackResult.originalFile.lastModified).toISOString();\n      }\n    }\n\n    // create our clip...\n    const clip = convertVideoFileToClip(newVideo);\n    clip.source = clip.source || {};\n    // it is important that this startsWith(filestackConfig.storeTo.path) in the clipApi logic\n    clip.source.src = filestackConfig.storeTo.clip.path + newVideo.filename;\n    clip.source.orig = filestackResult.key;\n    clip.source.filename = newVideo.filename;\n    clip.source.type = newVideo.type;\n    clip.source.loading = newVideo.loading;\n    clip.source.lastModifiedDate = newVideo.lastModifiedDate;\n\n    if (filestackResult.originalFile && filestackResult.originalFile.name) {\n      clip.source.file = filestackResult.originalFile;\n    }\n    // this must match for api to do its magic...\n    clip.id = Utils.removeFileExt(newVideo.filename);\n    clip.projectId = projectId;\n\n    if (typeof clip.filmingDate !== 'string') {\n      clip.filmingDate = Utils.getDateTimeString(clip.filmingDate);\n    }\n\n    DEBUG_LOGS && console.log(`${PAGE} createClipFromFilestack -> createClip:`, clip);\n\n    // kick off the createClip flow, subscribe to updates, add to mystack\n    // as soon as we return, the Capture Page will nav to the Stack Editor\n    return this.createClip(clip).then((newClip) => {\n      DEBUG_LOGS &&\n        console.log(`${PAGE} createClipFromFilestack -> now watch updates & addToStack (createClip result:`, newClip);\n      // watch changes and push to store\n      this.subClipsUpdated(newClip.projectId, newClip.id, userId);\n      // add the clip to the Editor\n      this.store.dispatch(mystackActions.addClip({ clip: newClip }));\n\n      try {\n        // analytics\n        const size =\n          newVideo && newVideo.size && typeof newVideo.size.toFixed === 'function' ? newVideo.size.toFixed(0) : false;\n        const seconds =\n          clip.source && clip.source.durationInSeconds && typeof clip.source.durationInSeconds.toFixed === 'function'\n            ? clip.source.durationInSeconds.toFixed(3)\n            : false;\n        this.analyticsService.clipUpload({\n          projectId: newClip.projectId,\n          id: newClip.id,\n          userId: newClip.userId,\n          ...(seconds && { seconds }),\n          ...(size && { size }),\n        });\n      } catch (error) {\n        console.warn(`Analytics error:`, error);\n      }\n    });\n  }\n\n  /**\n   * upload clip and add to store\n   * @param clip\n   * 1. Create the clip in DB\n   * 2.\n   */\n  createClip(clip: Clip): Promise<Clip> {\n    DEBUG_LOGS && console.log(`${PAGE} createClip`, clip);\n\n    // add the new HLS transcoding instruction\n    clip.hlsMeta = {\n      ...(clip.hlsMeta || {}),\n      ...HLS_META_TRANSCODE,\n    };\n\n    return this.clipsApi\n      .createClip(clip)\n      .then((res) => {\n        DEBUG_LOGS && console.log(`${PAGE} createClip res:`, res);\n        // add the clip to the Store\n        const newClip = new Clip(res);\n        this.store.dispatch(clipActions.addClip({ clip: newClip }));\n        return newClip;\n      })\n      .catch((err) => {\n        if (\n          err &&\n          Array.isArray(err.errors) &&\n          err.errors.length > 0 &&\n          err.errors[0] &&\n          err.errors[0].errorType &&\n          err.errors[0].errorType === 'DynamoDB:ConditionalCheckFailedException'\n        ) {\n          console.info(`${PAGE} createClip already Exists:`, err);\n          return new Clip(clip);\n        }\n        console.error(`${PAGE} createClip err:`, err);\n        if (err && Array.isArray(err.errors) && err.errors.length > 0 && err.errors[0].message) {\n          throw err.errors[0].message;\n        }\n        throw err;\n      });\n  }\n\n  /**\n   * clipUpdate GraphQL Subscription\n   */\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  subClipsUpdated(projectId: string, id = '', userId = '') {\n    const params: { projectId: string; id?: string } = {\n      projectId,\n    };\n    if (id) {\n      params.id = id;\n    }\n    // not currently using userId on this GQL subscription in the API\n    // if (userId) {  params.userId = userId; }\n\n    // keeping the sub variable local so there can be multiple open subs\n    const sub = this.clipsApi.subscribeClipsUpdated(params).subscribe({\n      next: (res) => {\n        // todo: convert to handleSubClips(res) when this not needed by editor\n        DEBUG_LOGS && console.log(`${PAGE} clipsUpdatedSubscription next:`, res);\n        if (!res || !res.id || !res.projectId) {\n          throw new Error('Missing clip id in subscription?');\n        }\n        const updates: Partial<Clip> = {};\n        let hlsMeta;\n        if (res.hlsMeta) {\n          try {\n            hlsMeta = typeof res.hlsMeta === 'string' ? JSON.parse(res.hlsMeta) : res.hlsMeta;\n            updates.hlsMeta = hlsMeta;\n          } catch (error) {\n            console.log(`Caught error during JSON.parse(res.hlsMeta):`, {\n              hlsMeta: res.hlsMeta,\n              error: error.message || error,\n            });\n          }\n        }\n        if (res.hlsSrc) {\n          updates.hlsSrc = res.hlsSrc;\n        }\n        if (Array.isArray(res.sources) && res.sources.length > 0) {\n          updates.sources = res.sources;\n        }\n        if (res.poster) {\n          updates.poster = res.poster;\n        }\n        if (res.duration) {\n          updates.duration = res.duration;\n        }\n\n        if (Object.keys(updates).length > 0) {\n          // take the updates and flow to store...\n          this.store.dispatch(\n            clipActions.updateClipTranscoding({\n              projectId: res.projectId,\n              id: res.id,\n              updates,\n            })\n          );\n          DEBUG_LOGS &&\n            console.log(`${PAGE} clipsUpdatedSubscription updateClipTranscoding:`, {\n              projectId: res.projectId,\n              id: res.id,\n              updates,\n            });\n        }\n\n        if (isHlsComplete(new Clip(updates))) {\n          // when complete, this.unsubClipsUpdated()\n          sub.unsubscribe();\n        }\n      },\n      error: (err) => {\n        console.warn(`${PAGE} clipsUpdatedSubscription ERROR:`, err);\n        this.sentryService.captureError(err);\n\n        if (projectId && id) {\n          this.store.dispatch(\n            clipActions.updateClipTranscoding({\n              projectId,\n              id,\n              updates: {\n                hlsMeta: {\n                  errorMessage: err.message || err.errorMessage || err,\n                },\n              },\n            })\n          );\n        }\n        sub.unsubscribe();\n      },\n      complete: () => {\n        DEBUG_LOGS && console.log(`${PAGE} clipsUpdatedSubscription => Complete`);\n      },\n    });\n  }\n  //end GraphQL Subscription\n\n  /**\n   * Take the poster string and return poster time from the transcoded name\n   * example poster path:\n   * https://videos.filmstacker.com/public/filmstacker-dev/jd_-_jd-note_202203021348/jd_-_jd-note_202203021348_thumb.0000000.jpg\n   */\n  getPosterTimeFromPoster(clip: Clip) {\n    if (clip && clip.poster) {\n      const sNum = clip.poster.substring(clip.poster.lastIndexOf('_thumb.'), clip.poster.lastIndexOf('.'));\n      // there shouldn't be any dashes anymore\n      // const sTime = sNum.replace(/-/g, '').replace(/0/gi, '');\n      try {\n        const parsed = parseInt(sNum, 10);\n        return parsed > 0 ? parsed : 0;\n      } catch (error) {\n        return 0;\n      }\n    }\n    return 0;\n  }\n\n  /**\n   * Update the poster image to match the new posterTime\n   *\n   * @todo verify it exists in s3\n   * potential ref: https://stackoverflow.com/questions/9815762/detect-when-an-image-fails-to-load-in-javascript\n   *\n   * example poster path:\n   * https://videos.filmstacker.com/public/filmstacker-dev/jd_-_jd-note_202203021348/jd_-_jd-note_202203021348_thumb.0000000.jpg\n   */\n  updatePosterToPosterTime(clip: Clip, posterTime: number) {\n    if (clip && clip.poster) {\n      let newPoster = clip.poster;\n      const sNum = ('0000000' + posterTime).slice(-7);\n      const startIndex = newPoster.lastIndexOf('_thumb.') + '_thumb.'.length;\n      const endIndex = newPoster.lastIndexOf('.');\n      newPoster = newPoster.slice(0, startIndex) + sNum + newPoster.slice(endIndex);\n      console.log(`${PAGE} change clip.poster (TODO: verify it exists in s3) posterTime: ${posterTime} to:`, {\n        sNum,\n        newPoster,\n      });\n      return newPoster;\n    } else {\n      console.warn(`${PAGE} missing clip.poster - unable to change`, clip);\n      return null;\n    }\n  }\n\n  /**\n   * @deprecated unused - use subClipsUpdated\n   * Do what we need with the clip we just received\n   */\n  private handleSubClips(res) {\n    DEBUG_LOGS && console.log(`${PAGE} handleSubClips next:`, res);\n    if (!res || !res.id || !res.projectId) {\n      throw new Error('Missing clip id in subscription?');\n    }\n    // if any values exist not null, add them as changes?\n    const updates: Partial<Clip> = {};\n    let hlsMeta;\n    if (res.hlsMeta) {\n      try {\n        hlsMeta = typeof res.hlsMeta === 'string' ? JSON.parse(res.hlsMeta) : res.hlsMeta;\n        updates.hlsMeta = hlsMeta;\n      } catch (error) {\n        console.log(`Caught error during JSON.parse(res.hlsMeta):`, {\n          hlsMeta: res.hlsMeta,\n          error: error.message || error,\n        });\n      }\n    }\n    if (res.hlsSrc) {\n      updates.hlsSrc = res.hlsSrc;\n    }\n    if (Array.isArray(res.sources) && res.sources.length > 0) {\n      updates.sources = res.sources;\n    }\n    if (res.poster) {\n      updates.poster = res.poster;\n    }\n    if (res.duration) {\n      updates.duration = res.duration;\n    }\n\n    // take the updates and flow to store...\n    // this.store.dispatch(clipActions.updateClipTranscoding({\n    //   projectId: res.projectId,\n    //   id: res.id,\n    //   updates,\n    // }));\n  }\n}\n","/** @format */\n\nimport { Directive, HostListener, Input } from '@angular/core';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Platform } from '@ionic/angular/standalone';\nimport {\n  MAX_VIDEO_CAPTURE_SIZE_MB,\n  MAX_VIDEO_CAPTURE_LENGTH_MINUTES,\n  MAX_VIDEO_CAPTURE_LENGTH_SECONDS,\n} from '@app/app.config';\nimport { SentryService } from '@app/core/services/sentry.service';\nimport { FilestackService, PickerFileMetadata } from '@app/core/filestack/filestack.service';\nimport { makeCaptureClipFilename } from '@app/clips/shared/clips.model';\nimport { environment, filestackConfig } from 'src/environments/environment';\nimport { BaseFileUpload } from './base-file-upload';\n\n@Directive({\n  selector: '[clipUpload]',\n  standalone: true,\n})\nexport class ClipUploadDirective extends BaseFileUpload {\n  @Input() projectId: string = '';\n  @Input() userId: string = 'anon';\n\n  /**\n   * Needs to align with transcode-pipeline-step-functions,\n   * only includes these:\n   * .mpg, .mp4, .m4v, .mov, .m2ts\n   *\n   * @note we can add video/mpeg (need to enforce .mpg ext & not .mpeg) & video/MP2T (enforce .m2ts ext) if desired\n   */\n  protected allowedFileTypes: string[] = ['video/mp4', 'video/x-m4v', 'video/quicktime'];\n  protected customPickerOptions = {\n    maxSize: MAX_VIDEO_CAPTURE_SIZE_MB * 1024 * 1024, // 600 MB\n    maxFiles: 24,\n    accept: 'video/*',\n    storeTo: {\n      path: filestackConfig.storeTo.clip.path, // note that projectId is not ready yet\n      container: filestackConfig.storeTo.clip.container,\n      region: filestackConfig.storeTo.clip.region,\n    },\n    uploadConfig: {\n      tags: {},\n    },\n    fromSources: ['local_file_system', 'video', 'googledrive', 'onedrive', 'facebook', 'box', 'url'],\n  };\n\n  constructor(\n    filestackService: FilestackService,\n    translate: TranslateService,\n    platform: Platform,\n    sentryService: SentryService\n  ) {\n    super(filestackService, translate, platform, sentryService);\n  }\n\n  @HostListener('click', ['$event'])\n  onClick(event: Event) {\n    // we need to get the projectId Input which is not available at construct...\n    if (this.projectId) {\n      const path = this.customPickerOptions.storeTo.path;\n      this.customPickerOptions.storeTo.path = path + this.projectId + '/';\n      this.customPickerOptions.uploadConfig.tags = { projectId: this.projectId };\n    }\n    this.openFilePicker(event);\n  }\n\n  makeFileUploadName(file: PickerFileMetadata, userId = this.userId) {\n    return makeCaptureClipFilename(userId, file.filename);\n  }\n\n  /**\n   * Called whenever user selects a file, used to verify the selected file before upload\n   * If you throw any error in this function it will reject the file selection.\n   * The error message will be displayed to the user as an alert.\n   * https://filestack.github.io/filestack-js/interfaces/pickeroptions.html#onfileselected\n   *\n   * @todo This currently only works for local uploads. Should migrate to acceptFn callback usage for files uploaded from local_source, as well as, from cloud providers.\n   * https://www.filestack.com/docs/uploads/pickers/web/#acceptFn\n   */\n  onFileSelected(file: PickerFileMetadata): Promise<unknown> {\n    return new Promise((resolve, reject) => {\n      if (!file || !file.originalFile) {\n        reject(this.translate.instant('FILE_UPLOAD.FILE_NOT_FOUND'));\n      }\n      if (this.allowedFileTypes && !this.allowedFileTypes.includes(file.mimetype)) {\n        reject(\n          this.translate.instant('FILE_UPLOAD.FORMAT_NOT_SUPPORTED', {\n            not_supported_file_type: file.mimetype,\n            supported_file_types: this.allowedFileTypes.join(', '),\n          })\n        );\n      }\n      this.checkVideoLength(file.originalFile)\n        .then(({ durationOK, durationMessage }) => {\n          console.log({ durationOK, durationMessage });\n          if (!durationOK) {\n            console.log(durationMessage);\n            reject(\n              `${this.translate.instant('FILE_UPLOAD.DURATION_EXCEEDS_LIMIT', {\n                minutes: MAX_VIDEO_CAPTURE_LENGTH_MINUTES,\n                duration: durationMessage,\n              })}. ${this.translate.instant('FILE_UPLOAD.FILENAME')}: '${file.filename || 'Unknown'}'`\n            );\n          }\n          const name = this.makeFileUploadName(file, this.userId);\n          console.log(`[Capture] onFileSelected filename:`, {\n            name,\n            durationMessage,\n            originalName: file.filename,\n            file,\n          });\n\n          // MVP-1092 Capture date not correct on iOS\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          const originalFile = file.originalFile as any;\n          const lastModifiedDate = originalFile?.lastModifiedDate ?? new Date();\n          const filelastModified = originalFile?.FilelastModified ?? Date.now();\n\n          /** @todo send Pinpoint event with details MVP-730 */\n          const pinpointEvent = {\n            platforms: this.platform.platforms(),\n            file: {\n              filename: file.filename || '',\n              name,\n              mimetype: file.mimetype || 'unknown',\n              FilelastModified: filelastModified,\n              lastModifiedDate: lastModifiedDate.toString(),\n              lastModifiedDateISO: lastModifiedDate.toISOString(),\n              filesize: file.size || 0,\n              source: file.source || 'unknown',\n            },\n          };\n\n          !environment.production && console.log(`todo: pinpoint event with clip details:`, pinpointEvent);\n\n          // add the size and modified date to the clip file so we can get it back when done\n          // eslint-disable-next-line @typescript-eslint/naming-convention\n          const upload_tags = this.customPickerOptions.uploadConfig.tags;\n\n          this.customPickerOptions.uploadConfig.tags = {\n            ...upload_tags,\n            lastModifiedDate: pinpointEvent.file.lastModifiedDateISO,\n            duration: durationMessage,\n            // filesize: pinpointEvent.file.filesize.toString(), // not needed...\n          };\n\n          // Return original object with updated filename:\n          resolve({ ...file, name });\n          // Or reject the selection with reject()\n        })\n        .catch((error) => {\n          console.error(error);\n          reject(this.translate.instant('ERRORS.CAUGHT_ERROR'));\n        });\n    });\n  }\n\n  /**\n   * Check the Duration of the File does not exceed the max\n   */\n  checkVideoLength(file): Promise<{ durationOK: boolean; durationMessage: string }> {\n    // return Promise.resolve({ durationOK: true, durationMessage: 'Skip for better UX?' });\n    return new Promise((resolve) => {\n      try {\n        // const reader = new FileReader(), // create a FileReader (not needed, instead using video element onDurationChange)\n        const video = document.createElement('video'); // create video element;\n        video.preload = 'metadata'; // more efficient\n\n        const checkDuration = () => {\n          if (!video || !video.duration) {\n            throw new Error('checkVideoLength checkDuration missing video or duration?');\n          }\n          const duration = video.duration;\n          console.log(`getDuration`, { video, duration, maxCaptureLength: MAX_VIDEO_CAPTURE_LENGTH_SECONDS });\n\n          // clean up\n          video.removeEventListener('durationchange', checkDuration);\n          (window.URL || window.webkitURL).revokeObjectURL(video.src);\n\n          if (duration <= MAX_VIDEO_CAPTURE_LENGTH_SECONDS) {\n            resolve({ durationOK: true, durationMessage: 'Ok to go!' });\n          } else {\n            try {\n              const durationMessage = (duration / 60).toFixed(1);\n              resolve({\n                durationOK: false,\n                durationMessage: `${durationMessage}`,\n              });\n            } catch (error) {\n              console.log(`parseFloat duration (${duration}) caught`, error);\n              resolve({\n                durationOK: false,\n                durationMessage: this.translate.instant('ERRORS.ERROR'),\n              });\n            }\n          }\n        };\n\n        video.addEventListener('durationchange', checkDuration);\n        // video.addEventListener('loadedmetadata', checkMetadata); // duration is more efficient and has all we need...\n\n        video.addEventListener('error', (event) => {\n          console.error(`checkVideoLength video listener caught error`, event);\n          const { name = 'unknown', type = 'unknown', size = 'unknown' } = file;\n          this.sentryService.captureMessage(\n            `checkVideoLength video listener caught error filename='${name}' fileType='${type}' filesize='${size}'`\n          );\n          resolve({\n            durationOK: false,\n            durationMessage: this.translate.instant('ERRORS.ERROR'),\n          });\n        });\n\n        video.src = (window.URL || window.webkitURL).createObjectURL(file);\n      } catch (error) {\n        const { name = 'unknown', type = 'unknown', size = 'unknown' } = file;\n        console.error(`checkVideoLength caught error filename='${name}' fileType='${type}' filesize='${size}'`);\n        console.error(`checkVideoLength caught`, error);\n        this.sentryService.captureError(error);\n        resolve({\n          durationOK: false,\n          durationMessage: this.translate.instant('ERRORS.ERROR'),\n        });\n      }\n    });\n  }\n}\n"],"x_google_ignoreList":[]}