Merge branch 'dev' of https://github.com/datarhei/restreamer-ui into dev
This commit is contained in:
commit
4cb38c0a1d
31
src/misc/CircularProgress.js
Normal file
31
src/misc/CircularProgress.js
Normal file
@ -0,0 +1,31 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default function Component({ color = 'inherit', value = -1 }) {
|
||||
if (value < 0) {
|
||||
return <CircularProgress color={color} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress variant="determinate" value={value} color={color} />
|
||||
<Box
|
||||
sx={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography component="div" sx={{ color: 'text.secondary' }}>
|
||||
{`${Math.round(value)}%`}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import videojs from 'video.js';
|
||||
import overlay from './videojs-overlay.es.js';
|
||||
import './videojs-overlay.es.js';
|
||||
import 'video.js/dist/video-js.min.css';
|
||||
import './video-js-skin-internal.min.css';
|
||||
import './video-js-skin-public.min.css';
|
||||
@ -28,8 +28,6 @@ export default function VideoJS({ type = 'videojs-internal', options = {}, onRea
|
||||
const videoElement = videoRef.current;
|
||||
if (!videoElement) return;
|
||||
|
||||
videojs.registerPlugin('overlay', overlay);
|
||||
|
||||
const player = (playerRef.current = videojs(videoElement, options, () => {
|
||||
onReady && onReady(player);
|
||||
}));
|
||||
|
||||
@ -63,7 +63,11 @@ export default function UploadButton({
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
let streamer = file.stream();
|
||||
|
||||
let reader = new FileReader();
|
||||
// read as blob: https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onloadend = async () => {
|
||||
if (reader.result === null) {
|
||||
@ -74,9 +78,11 @@ export default function UploadButton({
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onUpload(reader.result, type.extension, type.mimetype);
|
||||
};
|
||||
*/
|
||||
// transformStream in order to count transferred bytes: https://stackoverflow.com/questions/35711724/upload-progress-indicators-for-fetch
|
||||
// .pipeThrough(progressTrackingStream)
|
||||
onUpload(file, type.extension, type.mimetype);
|
||||
//};
|
||||
};
|
||||
|
||||
onStart();
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { fetch } from './fetch';
|
||||
|
||||
class API {
|
||||
constructor(address) {
|
||||
this.base = '/api';
|
||||
@ -277,12 +279,13 @@ class API {
|
||||
return await this._HEAD('/v3/fs/disk' + path);
|
||||
}
|
||||
|
||||
async DataPutFile(path, data) {
|
||||
async DataPutFile(path, data, onprogress = null) {
|
||||
return await this._PUT('/v3/fs/disk' + path, {
|
||||
headers: {
|
||||
'Content-Type': 'application/data',
|
||||
},
|
||||
body: data,
|
||||
onprogress: onprogress,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
81
src/utils/fetch.js
Normal file
81
src/utils/fetch.js
Normal file
@ -0,0 +1,81 @@
|
||||
const fetch = async (url, options = {}) => {
|
||||
options = {
|
||||
method: 'GET',
|
||||
...options,
|
||||
};
|
||||
|
||||
options.method = options.method.toUpperCase();
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.responseType = 'text';
|
||||
xhr.onload = () => {
|
||||
const response = {
|
||||
ok: false,
|
||||
headers: {
|
||||
get: function (key) {
|
||||
return this.data.get(key.toLowerCase());
|
||||
},
|
||||
data: xhr
|
||||
.getAllResponseHeaders()
|
||||
.split('\r\n')
|
||||
.reduce((result, current) => {
|
||||
let [name, value] = current.split(': ');
|
||||
result.set(name, value);
|
||||
return result;
|
||||
}, new Map()),
|
||||
},
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
data: xhr.response,
|
||||
json: function () {
|
||||
return JSON.parse(this.data);
|
||||
},
|
||||
text: function () {
|
||||
return this.data;
|
||||
},
|
||||
};
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
resolve(response);
|
||||
} else {
|
||||
response.ok = true;
|
||||
resolve(response);
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
reject({
|
||||
message: 'network error',
|
||||
});
|
||||
};
|
||||
if ('onprogress' in options && typeof options.onprogress == 'function') {
|
||||
const tracker = (event) => {
|
||||
if (!event.lengthComputable) {
|
||||
options.onprogress(false, 0, event.loaded);
|
||||
return;
|
||||
}
|
||||
|
||||
options.onprogress(true, event.loaded / event.total, event.total);
|
||||
};
|
||||
|
||||
if (options.method === 'GET') {
|
||||
xhr.onprogress = tracker;
|
||||
} else if (options.method === 'PUT' || options.method === 'POST') {
|
||||
xhr.upload.onprogress = tracker;
|
||||
}
|
||||
}
|
||||
xhr.open(options.method, url, true);
|
||||
if ('headers' in options) {
|
||||
for (const header in options.headers) {
|
||||
xhr.setRequestHeader(header, options.headers[header]);
|
||||
}
|
||||
}
|
||||
if ('body' in options) {
|
||||
xhr.send(options.body);
|
||||
} else {
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export { fetch };
|
||||
@ -2290,7 +2290,7 @@ class Restreamer {
|
||||
}
|
||||
|
||||
// Upload channel specific channel data
|
||||
async UploadData(channelid, name, data) {
|
||||
async UploadData(channelid, name, data, onprogress = null) {
|
||||
if (channelid.length === 0) {
|
||||
channelid = this.GetCurrentChannelID();
|
||||
}
|
||||
@ -2305,7 +2305,7 @@ class Restreamer {
|
||||
|
||||
const path = `/channels/${channel.channelid}/${name}`;
|
||||
|
||||
await this._uploadAssetData(path, data);
|
||||
await this._uploadAssetData(path, data, onprogress);
|
||||
|
||||
return path;
|
||||
}
|
||||
@ -3358,8 +3358,8 @@ class Restreamer {
|
||||
return true;
|
||||
}
|
||||
|
||||
async _uploadAssetData(remotePath, data) {
|
||||
await this._call(this.api.DataPutFile, remotePath, data);
|
||||
async _uploadAssetData(remotePath, data, onprogress = null) {
|
||||
await this._call(this.api.DataPutFile, remotePath, data, onprogress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import pkg from '../package.json';
|
||||
|
||||
const Core = '^16.11.0';
|
||||
const FFmpeg = '^5.1.0 || ^6.1.0';
|
||||
const FFmpeg = '^5.1.0 || ^6.1.0 || ^7.0.0';
|
||||
const UI = pkg.bundle ? pkg.bundle : pkg.name + ' v' + pkg.version;
|
||||
const Version = pkg.version;
|
||||
|
||||
|
||||
@ -234,8 +234,8 @@ export default function Profile({
|
||||
setSkillsRefresh(false);
|
||||
};
|
||||
|
||||
const handleStore = async (name, data) => {
|
||||
return await onStore(name, data);
|
||||
const handleStore = async (name, data, onprogress) => {
|
||||
return await onStore(name, data, onprogress);
|
||||
};
|
||||
|
||||
const handleEncoding = (type) => (encoder, decoder) => {
|
||||
|
||||
@ -78,8 +78,8 @@ export default function SourceSelect({
|
||||
await onRefresh();
|
||||
};
|
||||
|
||||
const handleStore = async (name, data) => {
|
||||
return await onStore(name, data);
|
||||
const handleStore = async (name, data, onprogress) => {
|
||||
return await onStore(name, data, onprogress);
|
||||
};
|
||||
|
||||
const handleProbe = async (settings, inputs) => {
|
||||
|
||||
@ -156,7 +156,7 @@ function SourceIcon(props) {
|
||||
const id = 'alsa';
|
||||
const name = <Trans>ALSA</Trans>;
|
||||
const capabilities = ['audio'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -221,7 +221,7 @@ function SourceIcon(props) {
|
||||
const id = 'avfoundation';
|
||||
const name = <Trans>AVFoundation</Trans>;
|
||||
const capabilities = ['audio', 'video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -4,12 +4,12 @@ import { Trans } from '@lingui/macro';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import Backdrop from '@mui/material/Backdrop';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Icon from '@mui/icons-material/Cached';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import CircularProgress from '../../../misc/CircularProgress';
|
||||
import Dialog from '../../../misc/modals/Dialog';
|
||||
import Filesize from '../../../misc/Filesize';
|
||||
import FormInlineButton from '../../../misc/FormInlineButton';
|
||||
@ -63,7 +63,10 @@ function Source({
|
||||
const classes = useStyles();
|
||||
settings = initSettings(settings);
|
||||
|
||||
const [$saving, setSaving] = React.useState(false);
|
||||
const [$progress, setProgress] = React.useState({
|
||||
enable: false,
|
||||
value: -1,
|
||||
});
|
||||
const [$error, setError] = React.useState({
|
||||
open: false,
|
||||
title: '',
|
||||
@ -71,7 +74,15 @@ function Source({
|
||||
});
|
||||
|
||||
const handleFileUpload = async (data, extension, mimetype) => {
|
||||
const path = await onStore('audioloop.source', data);
|
||||
const path = await onStore('audioloop.source', data, (computable, progress, total) => {
|
||||
setProgress((current) => {
|
||||
return {
|
||||
...current,
|
||||
enable: true,
|
||||
value: computable ? progress * 100 : -1,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
onChange({
|
||||
...settings,
|
||||
@ -79,11 +90,17 @@ function Source({
|
||||
mimetype: mimetype,
|
||||
});
|
||||
|
||||
setSaving(false);
|
||||
setProgress({
|
||||
...$progress,
|
||||
enable: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadStart = () => {
|
||||
setSaving(true);
|
||||
setProgress({
|
||||
...$progress,
|
||||
enable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadError = (title) => (err) => {
|
||||
@ -115,7 +132,10 @@ function Source({
|
||||
message = <Trans>Unknown upload error</Trans>;
|
||||
}
|
||||
|
||||
setSaving(false);
|
||||
setProgress({
|
||||
...$progress,
|
||||
enable: false,
|
||||
});
|
||||
|
||||
showUploadError(title, message);
|
||||
};
|
||||
@ -166,8 +186,8 @@ function Source({
|
||||
</FormInlineButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Backdrop open={$saving}>
|
||||
<CircularProgress color="inherit" />
|
||||
<Backdrop open={$progress.enable}>
|
||||
<CircularProgress color="inherit" value={$progress.value} />
|
||||
</Backdrop>
|
||||
<Dialog
|
||||
open={$error.open}
|
||||
@ -192,7 +212,7 @@ function SourceIcon(props) {
|
||||
const id = 'audioloop';
|
||||
const name = <Trans>Loop</Trans>;
|
||||
const capabilities = ['audio'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -130,7 +130,7 @@ function SourceIcon(props) {
|
||||
const id = 'fbdev';
|
||||
const name = <Trans>Framebuffer</Trans>;
|
||||
const capabilities = ['video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -1229,7 +1229,7 @@ function SourceIcon(props) {
|
||||
const id = 'network';
|
||||
const name = <Trans>Network source</Trans>;
|
||||
const capabilities = ['audio', 'video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -37,7 +37,7 @@ function SourceIcon(props) {
|
||||
const id = 'noaudio';
|
||||
const name = <Trans>No audio</Trans>;
|
||||
const capabilities = ['audio'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -136,7 +136,7 @@ function SourceIcon(props) {
|
||||
const id = 'raspicam';
|
||||
const name = <Trans>Raspberry Pi camera</Trans>;
|
||||
const capabilities = ['video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -193,7 +193,7 @@ function SourceIcon(props) {
|
||||
const id = 'sdp';
|
||||
const name = <Trans>SDP</Trans>;
|
||||
const capabilities = ['video', 'audio'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -145,7 +145,7 @@ function SourceIcon(props) {
|
||||
const id = 'video4linux2';
|
||||
const name = <Trans>Hardware device</Trans>;
|
||||
const capabilities = ['video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -37,7 +37,7 @@ function SourceIcon(props) {
|
||||
const id = 'videoaudio';
|
||||
const name = <Trans>Video source</Trans>;
|
||||
const capabilities = ['audio'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -4,12 +4,12 @@ import { Trans } from '@lingui/macro';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import Backdrop from '@mui/material/Backdrop';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Icon from '@mui/icons-material/Cached';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import CircularProgress from '../../../misc/CircularProgress';
|
||||
import Dialog from '../../../misc/modals/Dialog';
|
||||
import Filesize from '../../../misc/Filesize';
|
||||
import FormInlineButton from '../../../misc/FormInlineButton';
|
||||
@ -71,7 +71,10 @@ function Source({
|
||||
const classes = useStyles();
|
||||
settings = initSettings(settings);
|
||||
|
||||
const [$saving, setSaving] = React.useState(false);
|
||||
const [$progress, setProgress] = React.useState({
|
||||
enable: false,
|
||||
value: -1,
|
||||
});
|
||||
const [$error, setError] = React.useState({
|
||||
open: false,
|
||||
title: '',
|
||||
@ -79,7 +82,15 @@ function Source({
|
||||
});
|
||||
|
||||
const handleFileUpload = async (data, extension, mimetype) => {
|
||||
const path = await onStore('videoloop.source', data);
|
||||
const path = await onStore('videoloop.source', data, (computable, progress, total) => {
|
||||
setProgress((current) => {
|
||||
return {
|
||||
...current,
|
||||
enable: true,
|
||||
value: computable ? progress * 100 : -1,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
onChange({
|
||||
...settings,
|
||||
@ -87,11 +98,17 @@ function Source({
|
||||
mimetype: mimetype,
|
||||
});
|
||||
|
||||
setSaving(false);
|
||||
setProgress({
|
||||
...$progress,
|
||||
enable: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadStart = () => {
|
||||
setSaving(true);
|
||||
setProgress({
|
||||
...$progress,
|
||||
enable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadError = (title) => (err) => {
|
||||
@ -123,7 +140,10 @@ function Source({
|
||||
message = <Trans>Unknown upload error</Trans>;
|
||||
}
|
||||
|
||||
setSaving(false);
|
||||
setProgress({
|
||||
...$progress,
|
||||
enable: false,
|
||||
});
|
||||
|
||||
showUploadError(title, message);
|
||||
};
|
||||
@ -174,8 +194,8 @@ function Source({
|
||||
</FormInlineButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Backdrop open={$saving}>
|
||||
<CircularProgress color="inherit" />
|
||||
<Backdrop open={$progress.enable}>
|
||||
<CircularProgress color="inherit" value={$progress.value} />
|
||||
</Backdrop>
|
||||
<Dialog
|
||||
open={$error.open}
|
||||
@ -200,7 +220,7 @@ function SourceIcon(props) {
|
||||
const id = 'videoloop';
|
||||
const name = <Trans>Loop</Trans>;
|
||||
const capabilities = ['video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -180,7 +180,7 @@ function SourceIcon(props) {
|
||||
const id = 'virtualaudio';
|
||||
const name = <Trans>Virtual source</Trans>;
|
||||
const capabilities = ['audio'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -199,7 +199,7 @@ function SourceIcon(props) {
|
||||
const id = 'virtualvideo';
|
||||
const name = <Trans>Virtual source</Trans>;
|
||||
const capabilities = ['video'];
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0';
|
||||
const ffversion = '^4.1.0 || ^5.0.0 || ^6.1.0 || ^7.0.0';
|
||||
|
||||
const func = {
|
||||
initSettings,
|
||||
|
||||
@ -211,8 +211,8 @@ export default function Edit({ restreamer = null }) {
|
||||
setSkills(skills);
|
||||
};
|
||||
|
||||
const handleSourceStore = async (name, data) => {
|
||||
return await restreamer.UploadData('', name, data);
|
||||
const handleSourceStore = async (name, data, onprogress) => {
|
||||
return await restreamer.UploadData('', name, data, onprogress);
|
||||
};
|
||||
|
||||
const handleSourceProbe = async (inputs) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user