// @flow import { SITE_NAME, WEB_PUBLISH_SIZE_LIMIT_GB, SIMPLE_SITE } from 'config'; import type { Node } from 'react'; import * as ICONS from 'constants/icons'; import React, { useState, useEffect } from 'react'; import { regexInvalidURI } from 'util/lbryURI'; import PostEditor from 'component/postEditor'; import FileSelector from 'component/common/file-selector'; import Button from 'component/button'; import Card from 'component/common/card'; import { FormField } from 'component/common/form'; import Spinner from 'component/spinner'; import I18nMessage from 'component/i18nMessage'; import usePersistedState from 'effects/use-persisted-state'; import * as PUBLISH_MODES from 'constants/publish_types'; import PublishName from 'component/publishName'; import CopyableText from 'component/copyableText'; import Empty from 'component/common/empty'; import moment from 'moment'; import classnames from 'classnames'; import ReactPaginate from 'react-paginate'; import { SOURCE_NONE, SOURCE_SELECT, SOURCE_UPLOAD } from 'constants/publish_sources'; type Props = { uri: ?string, mode: ?string, name: ?string, title: ?string, filePath: string | WebFile, fileMimeType: ?string, isStillEditing: boolean, balance: number, doUpdatePublishForm: ({}) => void, disabled: boolean, publishing: boolean, doToast: ({ message: string, isError?: boolean }) => void, inProgress: boolean, doClearPublish: () => void, ffmpegStatus: any, optimize: boolean, size: number, duration: number, isVid: boolean, subtitle: string, setPublishMode: (string) => void, setPrevFileText: (string) => void, header: Node, livestreamData: LivestreamReplayData, isLivestreamClaim: boolean, checkLivestreams: (string, string) => void, channelName: string, channelId: string, isCheckingLivestreams: boolean, setWaitForFile: (boolean) => void, setOverMaxBitrate: (boolean) => void, fileSource: string, changeFileSource: (string) => void, inEditMode: boolean, }; function PublishFile(props: Props) { const { uri, mode, name, title, balance, filePath, fileMimeType, isStillEditing, doUpdatePublishForm: updatePublishForm, doToast, disabled, publishing, inProgress, doClearPublish, optimize, ffmpegStatus = {}, size, duration, isVid, setPublishMode, setPrevFileText, header, livestreamData, isLivestreamClaim, subtitle, checkLivestreams, channelId, channelName, isCheckingLivestreams, setWaitForFile, setOverMaxBitrate, fileSource, changeFileSource, inEditMode, } = props; const RECOMMENDED_BITRATE = 8500000; const MAX_BITRATE = 16500000; const TV_PUBLISH_SIZE_LIMIT_BYTES = WEB_PUBLISH_SIZE_LIMIT_GB * 1073741824; const TV_PUBLISH_SIZE_LIMIT_GB_STR = String(WEB_PUBLISH_SIZE_LIMIT_GB); const PROCESSING_MB_PER_SECOND = 0.5; const MINUTES_THRESHOLD = 30; const HOURS_THRESHOLD = MINUTES_THRESHOLD * 60; const MARKDOWN_FILE_EXTENSIONS = ['txt', 'md', 'markdown']; const sizeInMB = Number(size) / 1000000; const secondsToProcess = sizeInMB / PROCESSING_MB_PER_SECOND; const ffmpegAvail = ffmpegStatus.available; const [oversized, setOversized] = useState(false); const [currentFile, setCurrentFile] = useState(null); const [currentFileType, setCurrentFileType] = useState(null); const [optimizeAvail, setOptimizeAvail] = useState(false); const [userOptimize, setUserOptimize] = usePersistedState('publish-file-user-optimize', false); const UPLOAD_SIZE_MESSAGE = __('%SITE_NAME% uploads are limited to %limit% GB.', { SITE_NAME, limit: TV_PUBLISH_SIZE_LIMIT_GB_STR, }); const bitRate = getBitrate(size, duration); const bitRateIsOverMax = bitRate > MAX_BITRATE; const fileSelectorModes = [ { label: __('Upload'), actionName: SOURCE_UPLOAD, icon: ICONS.PUBLISH }, { label: __('Choose Replay'), actionName: SOURCE_SELECT, icon: ICONS.MENU }, { label: isLivestreamClaim ? __('Edit / Update') : __('None'), actionName: SOURCE_NONE }, ]; const livestreamDataStr = JSON.stringify(livestreamData); const hasLivestreamData = livestreamData && Boolean(livestreamData.length); const [showSourceSelector, setShowSourceSelector] = useState(false); // const [showFileUpdate, setShowFileUpdate] = useState(false); const [selectedFileIndex, setSelectedFileIndex] = useState(null); const PAGE_SIZE = 4; const [currentPage, setCurrentPage] = useState(1); const totalPages = hasLivestreamData && livestreamData.length > PAGE_SIZE ? Math.ceil(livestreamData.length / PAGE_SIZE) : 1; // Reset filePath if publish mode changed useEffect(() => { if (mode === PUBLISH_MODES.POST) { if (currentFileType !== 'text/markdown' && !isStillEditing) { updatePublishForm({ filePath: '' }); } } else if (mode === PUBLISH_MODES.LIVESTREAM) { updatePublishForm({ filePath: '' }); } }, [currentFileType, mode, isStillEditing, updatePublishForm]); // Reset title when form gets cleared useEffect(() => { updatePublishForm({ title: title }); }, [filePath]); // Initialize default file source state for each mode. useEffect(() => { setShowSourceSelector(false); switch (mode) { case PUBLISH_MODES.LIVESTREAM: if (inEditMode) { changeFileSource(SOURCE_SELECT); setShowSourceSelector(true); } else { changeFileSource(SOURCE_NONE); } break; case PUBLISH_MODES.POST: changeFileSource(SOURCE_NONE); break; case PUBLISH_MODES.FILE: if (hasLivestreamData) setShowSourceSelector(true); changeFileSource(SOURCE_UPLOAD); break; default: changeFileSource(SOURCE_UPLOAD); } }, [mode, hasLivestreamData]); // eslint-disable-line react-hooks/exhaustive-deps const normalizeUrlForProtocol = (url) => { if (url.startsWith('https://')) { return url; } else { if (url.startsWith('http://')) { return url; } else if (url) { return `https://${url}`; } else return __('Click Check for Replays to update...'); } }; // update remoteUrl when replay selected useEffect(() => { const livestreamData = JSON.parse(livestreamDataStr); if (selectedFileIndex !== null && livestreamData && livestreamData.length) { updatePublishForm({ remoteFileUrl: normalizeUrlForProtocol(livestreamData[selectedFileIndex].data.fileLocation), }); } }, [selectedFileIndex, updatePublishForm, livestreamDataStr]); useEffect(() => { if (!filePath || filePath === '') { setCurrentFile(''); setOversized(false); setOverMaxBitrate(false); updateFileInfo(0, 0, false); } else if (typeof filePath !== 'string') { // Update currentFile file if (filePath.name !== currentFile && filePath.path !== currentFile) { handleFileChange(filePath); } } }, [filePath, currentFile, doToast, updatePublishForm]); useEffect(() => { const isOptimizeAvail = currentFile && currentFile !== '' && isVid && ffmpegAvail; const finalOptimizeState = isOptimizeAvail && userOptimize; setOptimizeAvail(isOptimizeAvail); updatePublishForm({ optimize: finalOptimizeState }); }, [currentFile, filePath, isVid, ffmpegAvail, userOptimize, updatePublishForm]); useEffect(() => { setOverMaxBitrate(bitRateIsOverMax); }, [bitRateIsOverMax]); function updateFileInfo(duration, size, isvid) { updatePublishForm({ fileDur: duration, fileSize: size, fileVid: isvid }); } function handlePaginateReplays(page) { setCurrentPage(page); } function getBitrate(size, duration) { const s = Number(size); const d = Number(duration); if (s && d) { return (s * 8) / d; } else { return 0; } } function getTimeForMB(s) { if (s < MINUTES_THRESHOLD) { return Math.floor(secondsToProcess); } else if (s >= MINUTES_THRESHOLD && s < HOURS_THRESHOLD) { return Math.floor(secondsToProcess / 60); } else { return Math.floor(secondsToProcess / 60 / 60); } } function getUnitsForMB(s) { if (s < MINUTES_THRESHOLD) { if (secondsToProcess > 1) return __('seconds'); return __('second'); } else if (s >= MINUTES_THRESHOLD && s < HOURS_THRESHOLD) { if (Math.floor(secondsToProcess / 60) > 1) return __('minutes'); return __('minute'); } else { if (Math.floor(secondsToProcess / 3600) > 1) return __('hours'); return __('hour'); } } function getUploadMessage() { // @if TARGET='web' if (oversized) { return (

{UPLOAD_SIZE_MESSAGE}{' '}