diff --git a/src/misc/CircularProgress.js b/src/misc/CircularProgress.js
new file mode 100644
index 0000000..a37c936
--- /dev/null
+++ b/src/misc/CircularProgress.js
@@ -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 ;
+ }
+
+ return (
+
+
+
+
+ {`${Math.round(value)}%`}
+
+
+
+ );
+}
diff --git a/src/misc/Player/videojs.js b/src/misc/Player/videojs.js
index 87bf9c8..22c6c0d 100644
--- a/src/misc/Player/videojs.js
+++ b/src/misc/Player/videojs.js
@@ -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);
}));
diff --git a/src/misc/UploadButton.js b/src/misc/UploadButton.js
index 181cdfe..96b8693 100644
--- a/src/misc/UploadButton.js
+++ b/src/misc/UploadButton.js
@@ -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();
diff --git a/src/utils/api.js b/src/utils/api.js
index a5f4f41..b175259 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -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,
});
}
diff --git a/src/utils/fetch.js b/src/utils/fetch.js
new file mode 100644
index 0000000..bf99d27
--- /dev/null
+++ b/src/utils/fetch.js
@@ -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 };
diff --git a/src/utils/restreamer.js b/src/utils/restreamer.js
index 7fd8d40..5f120a8 100644
--- a/src/utils/restreamer.js
+++ b/src/utils/restreamer.js
@@ -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;
}
diff --git a/src/version.js b/src/version.js
index b7c73df..9e95f2e 100644
--- a/src/version.js
+++ b/src/version.js
@@ -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;
diff --git a/src/views/Edit/Profile.js b/src/views/Edit/Profile.js
index b7b3e57..1f7d986 100644
--- a/src/views/Edit/Profile.js
+++ b/src/views/Edit/Profile.js
@@ -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) => {
diff --git a/src/views/Edit/SourceSelect.js b/src/views/Edit/SourceSelect.js
index d92a590..2e52cd6 100644
--- a/src/views/Edit/SourceSelect.js
+++ b/src/views/Edit/SourceSelect.js
@@ -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) => {
diff --git a/src/views/Edit/Sources/ALSA.js b/src/views/Edit/Sources/ALSA.js
index a2e9fd4..c57a5b1 100644
--- a/src/views/Edit/Sources/ALSA.js
+++ b/src/views/Edit/Sources/ALSA.js
@@ -156,7 +156,7 @@ function SourceIcon(props) {
const id = 'alsa';
const name = ALSA;
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,
diff --git a/src/views/Edit/Sources/AVFoundation.js b/src/views/Edit/Sources/AVFoundation.js
index 1cde83b..45e739f 100644
--- a/src/views/Edit/Sources/AVFoundation.js
+++ b/src/views/Edit/Sources/AVFoundation.js
@@ -221,7 +221,7 @@ function SourceIcon(props) {
const id = 'avfoundation';
const name = AVFoundation;
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,
diff --git a/src/views/Edit/Sources/AudioLoop.js b/src/views/Edit/Sources/AudioLoop.js
index c88deca..23cefaf 100644
--- a/src/views/Edit/Sources/AudioLoop.js
+++ b/src/views/Edit/Sources/AudioLoop.js
@@ -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 = Unknown upload error;
}
- setSaving(false);
+ setProgress({
+ ...$progress,
+ enable: false,
+ });
showUploadError(title, message);
};
@@ -166,8 +186,8 @@ function Source({
-
-
+
+