667 lines
24 KiB
JavaScript
667 lines
24 KiB
JavaScript
// @flow
|
||
import { SITE_NAME } from 'config';
|
||
import type { Node } from 'react';
|
||
import * as ICONS from 'constants/icons';
|
||
import React, { useState, useEffect } from 'react';
|
||
import { regexInvalidURI } from 'lbry-redux';
|
||
import PostEditor from 'component/postEditor';
|
||
import FileSelector from 'component/common/file-selector';
|
||
import Button from 'component/button';
|
||
import Card from 'component/common/card';
|
||
import { Form, 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 moment from 'moment';
|
||
import classnames from 'classnames';
|
||
import ReactPaginate from 'react-paginate';
|
||
|
||
type Props = {
|
||
uri: ?string,
|
||
mode: ?string,
|
||
name: ?string,
|
||
title: ?string,
|
||
filePath: string | WebFile,
|
||
fileMimeType: ?string,
|
||
isStillEditing: boolean,
|
||
balance: number,
|
||
updatePublishForm: ({}) => void,
|
||
disabled: boolean,
|
||
publishing: boolean,
|
||
showToast: (string) => void,
|
||
inProgress: boolean,
|
||
clearPublish: () => 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,
|
||
remoteUrl?: string,
|
||
checkLivestreams: () => void,
|
||
isCheckingLivestreams: boolean,
|
||
};
|
||
|
||
function PublishFile(props: Props) {
|
||
const {
|
||
uri,
|
||
mode,
|
||
name,
|
||
title,
|
||
balance,
|
||
filePath,
|
||
fileMimeType,
|
||
isStillEditing,
|
||
updatePublishForm,
|
||
disabled,
|
||
publishing,
|
||
inProgress,
|
||
clearPublish,
|
||
optimize,
|
||
ffmpegStatus = {},
|
||
size,
|
||
duration,
|
||
isVid,
|
||
setPublishMode,
|
||
setPrevFileText,
|
||
header,
|
||
livestreamData,
|
||
isLivestreamClaim,
|
||
subtitle,
|
||
remoteUrl,
|
||
checkLivestreams,
|
||
isCheckingLivestreams,
|
||
} = props;
|
||
|
||
const SOURCE_NONE = 'none';
|
||
const SOURCE_SELECT = 'select';
|
||
const SOURCE_UPLOAD = 'upload';
|
||
|
||
const RECOMMENDED_BITRATE = 6000000;
|
||
const TV_PUBLISH_SIZE_LIMIT: number = 4294967296;
|
||
const TV_PUBLISH_SIZE_LIMIT_STR_GB = '4';
|
||
const PAGE_SIZE = 4;
|
||
|
||
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 fileSelectorModes = [
|
||
{ label: __('Select Replay'), actionName: SOURCE_SELECT, icon: ICONS.MENU },
|
||
{ label: __('Upload'), actionName: SOURCE_UPLOAD, icon: ICONS.PUBLISH },
|
||
{ label: __('Not Yet'), actionName: SOURCE_NONE, icon: ICONS.REMOVE },
|
||
];
|
||
|
||
const hasLivestreamData = livestreamData && Boolean(livestreamData.length);
|
||
const showSourceSelector = isLivestreamClaim && hasLivestreamData;
|
||
|
||
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 [fileSelectSource, setFileSelectSource] = useState(
|
||
IS_WEB && showSourceSelector ? SOURCE_SELECT : SOURCE_UPLOAD
|
||
);
|
||
// const [showFileUpdate, setShowFileUpdate] = useState(false);
|
||
const [selectedFileIndex, setSelectedFileIndex] = useState(null);
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const totalPages =
|
||
hasLivestreamData && livestreamData.length > PAGE_SIZE ? Math.ceil(livestreamData.length / PAGE_SIZE) : 1;
|
||
|
||
const UPLOAD_SIZE_MESSAGE = __(
|
||
'%SITE_NAME% uploads are limited to %limit% GB. Download the app for unrestricted publishing.',
|
||
{ SITE_NAME, limit: TV_PUBLISH_SIZE_LIMIT_STR_GB }
|
||
);
|
||
|
||
// 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]);
|
||
|
||
// set default file source to select if necessary
|
||
useEffect(() => {
|
||
if (hasLivestreamData && isLivestreamClaim) {
|
||
setFileSelectSource(SOURCE_SELECT);
|
||
} else if (isLivestreamClaim) {
|
||
setFileSelectSource(SOURCE_NONE);
|
||
}
|
||
}, [hasLivestreamData, isLivestreamClaim, setFileSelectSource]);
|
||
|
||
const normalizeUrlForProtocol = (url) => {
|
||
if (url.startsWith('https://')) {
|
||
return url;
|
||
} else {
|
||
if (url.startsWith('http://')) {
|
||
return url;
|
||
} else {
|
||
return `https://${url}`;
|
||
}
|
||
}
|
||
};
|
||
// update remoteUrl when replay selected
|
||
useEffect(() => {
|
||
if (selectedFileIndex !== null) {
|
||
updatePublishForm({
|
||
remoteFileUrl: normalizeUrlForProtocol(livestreamData[selectedFileIndex].data.fileLocation),
|
||
});
|
||
}
|
||
}, [selectedFileIndex, updatePublishForm]);
|
||
|
||
useEffect(() => {
|
||
if (!filePath || filePath === '') {
|
||
setCurrentFile('');
|
||
setOversized(false);
|
||
updateFileInfo(0, 0, false);
|
||
} else if (typeof filePath !== 'string') {
|
||
// Update currentFile file
|
||
if (filePath.name !== currentFile && filePath.path !== currentFile) {
|
||
handleFileChange(filePath);
|
||
}
|
||
}
|
||
}, [filePath, currentFile, handleFileChange, updateFileInfo]);
|
||
|
||
useEffect(() => {
|
||
const isOptimizeAvail = currentFile && currentFile !== '' && isVid && ffmpegAvail;
|
||
const finalOptimizeState = isOptimizeAvail && userOptimize;
|
||
|
||
setOptimizeAvail(isOptimizeAvail);
|
||
updatePublishForm({ optimize: finalOptimizeState });
|
||
}, [currentFile, filePath, isVid, ffmpegAvail, userOptimize, updatePublishForm]);
|
||
|
||
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 getSourceMessage() {
|
||
if (remoteUrl || filePath) {
|
||
return __('Replay selected for update');
|
||
} else {
|
||
return __('No replay selected');
|
||
}
|
||
}
|
||
function getUploadMessage() {
|
||
// @if TARGET='web'
|
||
if (oversized) {
|
||
return (
|
||
<p className="help--error">
|
||
{UPLOAD_SIZE_MESSAGE}{' '}
|
||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
||
</p>
|
||
);
|
||
}
|
||
// @endif
|
||
if (isVid && duration && getBitrate(size, duration) > RECOMMENDED_BITRATE) {
|
||
return (
|
||
<p className="help--warning">
|
||
{__('Your video has a bitrate over 5 Mbps. We suggest transcoding to provide viewers the best experience.')}{' '}
|
||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
||
</p>
|
||
);
|
||
}
|
||
|
||
if (isVid && !duration) {
|
||
return (
|
||
<p className="help--warning">
|
||
{__(
|
||
'Your video may not be the best format. Use MP4s in H264/AAC format and a friendly bitrate (under 5 Mbps) and resolution (720p) for more reliable streaming.'
|
||
)}{' '}
|
||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
||
</p>
|
||
);
|
||
}
|
||
|
||
if (!!isStillEditing && name) {
|
||
if (isLivestreamClaim) {
|
||
return (
|
||
<p className="help">{__('You can upload your own recording or select a replay when your stream is over')}</p>
|
||
);
|
||
}
|
||
return (
|
||
<p className="help">
|
||
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
|
||
</p>
|
||
);
|
||
}
|
||
// @if TARGET='web'
|
||
if (!isStillEditing) {
|
||
return (
|
||
<p className="help">
|
||
{__(
|
||
'For video content, use MP4s in H264/AAC format and a friendly bitrate (under 5 Mbps) and resolution (720p) for more reliable streaming. %SITE_NAME% uploads are restricted to %limit% GB.',
|
||
{ SITE_NAME, limit: TV_PUBLISH_SIZE_LIMIT_STR_GB }
|
||
)}{' '}
|
||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
||
</p>
|
||
);
|
||
}
|
||
// @endif
|
||
|
||
// @if TARGET='app'
|
||
if (!isStillEditing) {
|
||
return (
|
||
<p className="help">
|
||
{__(
|
||
'For video content, use MP4s in H264/AAC format and a friendly bitrate (under 5 Mbps) and resolution (720p) for more reliable streaming.'
|
||
)}{' '}
|
||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
||
</p>
|
||
);
|
||
}
|
||
// @endif
|
||
}
|
||
|
||
function parseName(newName) {
|
||
let INVALID_URI_CHARS = new RegExp(regexInvalidURI, 'gu');
|
||
return newName.replace(INVALID_URI_CHARS, '-');
|
||
}
|
||
|
||
function handleFileSource(source) {
|
||
if (source === SOURCE_NONE) {
|
||
// clear files and remotes...
|
||
// https://github.com/lbryio/lbry-desktop/issues/5855
|
||
// publish is trying to use one field to share html file blob and string and such
|
||
// $FlowFixMe
|
||
handleFileChange(false);
|
||
updatePublishForm({ remoteFileUrl: undefined });
|
||
} else if (source === SOURCE_UPLOAD) {
|
||
updatePublishForm({ remoteFileUrl: undefined });
|
||
} else if (source === SOURCE_SELECT) {
|
||
// $FlowFixMe
|
||
handleFileChange(false);
|
||
if (selectedFileIndex !== null) {
|
||
updatePublishForm({ remoteFileUrl: livestreamData[selectedFileIndex].data.fileLocation });
|
||
}
|
||
}
|
||
setFileSelectSource(source);
|
||
}
|
||
|
||
function handleTitleChange(event) {
|
||
const title = event.target.value;
|
||
// Update title
|
||
updatePublishForm({ title });
|
||
}
|
||
|
||
function handleFileReaderLoaded(event: ProgressEvent) {
|
||
// See: https://github.com/facebook/flow/issues/3470
|
||
if (event.target instanceof FileReader) {
|
||
const text = event.target.result;
|
||
updatePublishForm({ fileText: text });
|
||
setPublishMode(PUBLISH_MODES.POST);
|
||
}
|
||
}
|
||
|
||
function handleFileChange(file: WebFile) {
|
||
const { showToast } = props;
|
||
window.URL = window.URL || window.webkitURL;
|
||
setOversized(false);
|
||
|
||
// select file, start to select a new one, then cancel
|
||
if (!file) {
|
||
if (isStillEditing) {
|
||
updatePublishForm({ filePath: '' });
|
||
} else {
|
||
updatePublishForm({ filePath: '', name: '' });
|
||
}
|
||
return;
|
||
}
|
||
|
||
// if video, extract duration so we can warn about bitrateif (typeof file !== 'string') {
|
||
const contentType = file.type && file.type.split('/');
|
||
const isVideo = contentType && contentType[0] === 'video';
|
||
const isMp4 = contentType && contentType[1] === 'mp4';
|
||
|
||
let isTextPost = false;
|
||
|
||
if (contentType && contentType[0] === 'text') {
|
||
isTextPost = contentType[1] === 'plain' || contentType[1] === 'markdown';
|
||
setCurrentFileType(contentType);
|
||
} else if (file.name) {
|
||
// If user's machine is missign a valid content type registration
|
||
// for markdown content: text/markdown, file extension will be used instead
|
||
const extension = file.name.split('.').pop();
|
||
isTextPost = MARKDOWN_FILE_EXTENSIONS.includes(extension);
|
||
}
|
||
|
||
if (isVideo) {
|
||
if (isMp4) {
|
||
const video = document.createElement('video');
|
||
video.preload = 'metadata';
|
||
video.onloadedmetadata = () => {
|
||
updateFileInfo(video.duration, file.size, isVideo);
|
||
window.URL.revokeObjectURL(video.src);
|
||
};
|
||
video.onerror = () => {
|
||
updateFileInfo(0, file.size, isVideo);
|
||
};
|
||
video.src = window.URL.createObjectURL(file);
|
||
} else {
|
||
updateFileInfo(0, file.size, isVideo);
|
||
}
|
||
} else {
|
||
updateFileInfo(0, file.size, isVideo);
|
||
}
|
||
|
||
if (isTextPost) {
|
||
// Create reader
|
||
const reader = new FileReader();
|
||
// Handler for file reader
|
||
reader.addEventListener('load', handleFileReaderLoaded);
|
||
// Read file contents
|
||
reader.readAsText(file);
|
||
setCurrentFileType('text/markdown');
|
||
} else {
|
||
setPublishMode(PUBLISH_MODES.FILE);
|
||
}
|
||
|
||
// @if TARGET='web'
|
||
// we only need to enforce file sizes on 'web'
|
||
if (file.size && Number(file.size) > TV_PUBLISH_SIZE_LIMIT) {
|
||
setOversized(true);
|
||
showToast(__(UPLOAD_SIZE_MESSAGE));
|
||
updatePublishForm({ filePath: '', name: '' });
|
||
return;
|
||
}
|
||
// @endif
|
||
|
||
const publishFormParams: { filePath: string | WebFile, name?: string, optimize?: boolean } = {
|
||
// if electron, we'll set filePath to the path string because SDK is handling publishing.
|
||
// File.path will be undefined from web due to browser security, so it will default to the File Object.
|
||
filePath: file.path || file,
|
||
};
|
||
// Strip off extention and replace invalid characters
|
||
let fileName = name || (file.name && file.name.substr(0, file.name.lastIndexOf('.'))) || '';
|
||
|
||
if (!isStillEditing) {
|
||
publishFormParams.name = parseName(fileName);
|
||
}
|
||
|
||
// File path is not supported on web for security reasons so we use the name instead.
|
||
setCurrentFile(file.path || file.name);
|
||
updatePublishForm(publishFormParams);
|
||
}
|
||
|
||
const showFileUpload = mode === PUBLISH_MODES.FILE || (mode === PUBLISH_MODES.LIVESTREAM && hasLivestreamData);
|
||
const isPublishPost = mode === PUBLISH_MODES.POST;
|
||
|
||
return (
|
||
<Card
|
||
className={disabled || balance === 0 ? 'card--disabled' : ''}
|
||
title={
|
||
<div>
|
||
{header} {/* display mode buttons from parent */}
|
||
{publishing && <Spinner type={'small'} />}
|
||
{inProgress && (
|
||
<div>
|
||
<Button button="close" label={__('New')} icon={ICONS.REFRESH} onClick={clearPublish} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
}
|
||
subtitle={subtitle || (isStillEditing && __('You are currently editing your upload.'))}
|
||
actions={
|
||
<React.Fragment>
|
||
<PublishName uri={uri} />
|
||
<FormField
|
||
type="text"
|
||
name="content_title"
|
||
label={__('Title')}
|
||
placeholder={__('Descriptive titles work best')}
|
||
disabled={disabled}
|
||
value={title}
|
||
onChange={handleTitleChange}
|
||
/>
|
||
{/* Decide whether to show file upload or replay selector */}
|
||
{/* @if TARGET='web' */}
|
||
<>
|
||
{showSourceSelector && (
|
||
<fieldset-section>
|
||
<div className="section__actions--between">
|
||
<div>
|
||
<label>{__('Add replay video')}</label>
|
||
<div>
|
||
{fileSelectorModes.map((fmode) => (
|
||
<Button
|
||
key={fmode.label}
|
||
icon={fmode.icon}
|
||
iconSize={18}
|
||
label={fmode.label}
|
||
button="alt"
|
||
onClick={() => {
|
||
// $FlowFixMe
|
||
handleFileSource(fmode.actionName);
|
||
}}
|
||
className={classnames('button-toggle', {
|
||
'button-toggle--active': fileSelectSource === fmode.actionName,
|
||
})}
|
||
/>
|
||
))}
|
||
</div>
|
||
<div className="help--inline">{getSourceMessage()}</div>
|
||
</div>
|
||
<Button
|
||
button="secondary"
|
||
label={__('Check for Replays')}
|
||
disabled={isCheckingLivestreams}
|
||
icon={ICONS.REFRESH}
|
||
onClick={() => checkLivestreams()}
|
||
/>
|
||
</div>
|
||
</fieldset-section>
|
||
)}
|
||
|
||
{fileSelectSource === SOURCE_UPLOAD && showFileUpload && (
|
||
<>
|
||
<FileSelector
|
||
label={__('Video file')}
|
||
disabled={disabled}
|
||
currentPath={currentFile}
|
||
onFileChosen={handleFileChange}
|
||
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
|
||
accept="video/mp4,video/x-m4v,video/*"
|
||
placeholder={__('Select video file to upload')}
|
||
/>
|
||
{getUploadMessage()}
|
||
</>
|
||
)}
|
||
{fileSelectSource === SOURCE_SELECT && showFileUpload && (
|
||
<>
|
||
<fieldset-section>
|
||
<label>{__('Select Replay')}</label>
|
||
<div className="table__wrapper">
|
||
<table className="table table--livestream-data">
|
||
<tbody>
|
||
{livestreamData &&
|
||
livestreamData
|
||
.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE)
|
||
.map((item, i) => (
|
||
<tr
|
||
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
|
||
key={item.id}
|
||
className={classnames('livestream__data-row', {
|
||
'livestream__data-row--selected':
|
||
selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i,
|
||
})}
|
||
>
|
||
<td>
|
||
<FormField
|
||
type="radio"
|
||
checked={selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i}
|
||
label={null}
|
||
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
|
||
className="livestream__data-row-radio"
|
||
/>
|
||
</td>
|
||
<td>
|
||
<div className="livestream_thumb_container">
|
||
{item.data.thumbnails.slice(0, 3).map((thumb) => (
|
||
<img key={thumb} className="livestream___thumb" src={thumb} />
|
||
))}
|
||
</div>
|
||
</td>
|
||
<td>
|
||
{`${Math.floor(item.data.fileDuration / 60)} ${
|
||
Math.floor(item.data.fileDuration / 60) > 1 ? __('minutes') : __('minute')
|
||
}`}
|
||
<div className="table__item-label">
|
||
{`${moment(item.data.uploadedAt).from(moment())}`}
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<CopyableText
|
||
primaryButton
|
||
copyable={normalizeUrlForProtocol(item.data.fileLocation)}
|
||
snackMessage={__('Url copied.')}
|
||
/>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</fieldset-section>
|
||
<Form style={totalPages <= 1 ? { display: 'none' } : null} onSubmit={handlePaginateReplays}>
|
||
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
|
||
<fieldset-section>
|
||
<ReactPaginate
|
||
pageCount={totalPages}
|
||
pageRangeDisplayed={2}
|
||
previousLabel="‹"
|
||
nextLabel="›"
|
||
activeClassName="pagination__item--selected"
|
||
pageClassName="pagination__item"
|
||
previousClassName="pagination__item pagination__item--previous"
|
||
nextClassName="pagination__item pagination__item--next"
|
||
breakClassName="pagination__item pagination__item--break"
|
||
marginPagesDisplayed={2}
|
||
onPageChange={(e) => handlePaginateReplays(e.selected + 1)}
|
||
forcePage={currentPage - 1}
|
||
initialPage={currentPage - 1}
|
||
containerClassName="pagination"
|
||
/>
|
||
</fieldset-section>
|
||
</fieldset-group>
|
||
</Form>
|
||
</>
|
||
)}
|
||
</>
|
||
{/* @endif */}
|
||
{/* @if TARGET='app' */}
|
||
{showFileUpload && (
|
||
<FileSelector
|
||
label={__('Video file')}
|
||
disabled={disabled}
|
||
currentPath={currentFile}
|
||
onFileChosen={handleFileChange}
|
||
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
|
||
accept="video/mp4,video/x-m4v,video/*"
|
||
placeholder={__('Select video file to upload')}
|
||
/>
|
||
)}
|
||
{showFileUpload && (
|
||
<FormField
|
||
type="checkbox"
|
||
checked={userOptimize}
|
||
disabled={!optimizeAvail}
|
||
onChange={() => setUserOptimize(!userOptimize)}
|
||
label={__('Optimize and transcode video')}
|
||
name="optimize"
|
||
/>
|
||
)}
|
||
{showFileUpload && !ffmpegAvail && (
|
||
<p className="help">
|
||
<I18nMessage
|
||
tokens={{
|
||
settings_link: <Button button="link" navigate="/$/settings" label={__('Settings')} />,
|
||
}}
|
||
>
|
||
FFmpeg not configured. More in %settings_link%.
|
||
</I18nMessage>
|
||
</p>
|
||
)}
|
||
{showFileUpload && Boolean(size) && ffmpegAvail && optimize && isVid && (
|
||
<p className="help">
|
||
<I18nMessage
|
||
tokens={{
|
||
size: Math.ceil(sizeInMB),
|
||
processTime: getTimeForMB(sizeInMB),
|
||
units: getUnitsForMB(sizeInMB),
|
||
}}
|
||
>
|
||
Transcoding this %size% MB file should take under %processTime% %units%.
|
||
</I18nMessage>
|
||
</p>
|
||
)}
|
||
{/* @endif */}
|
||
{isPublishPost && (
|
||
<PostEditor
|
||
label={__('Post --[noun, markdown post tab button]--')}
|
||
uri={uri}
|
||
disabled={disabled}
|
||
fileMimeType={fileMimeType}
|
||
setPrevFileText={setPrevFileText}
|
||
setCurrentFileType={setCurrentFileType}
|
||
/>
|
||
)}
|
||
</React.Fragment>
|
||
}
|
||
/>
|
||
);
|
||
}
|
||
|
||
export default PublishFile;
|