how to turn off buffering in react-player? - reactjs

when I try to play the next video it does not start and I guess the problem is buffering.
P.S my url is video.m3u8 files
It works fine, but when i change url nothing happens, i would like to know how can i stop current video and load a new one whe, url changes ?
here's my rewind function
const showVideo = async () => {
sessionStorage.setItem("sPlayerLinkId", params.id);
const body = new FormData();
const mac = window.TvipStb.getMainMacAddress();
body.append("link_id", params.id);
body.append("mac", mac);
let response = await fetch(getVideo, {
method: "POST",
body: body,
});
let data = await response.json();
if (data.error) {
openDialog("crush");
return 0;
}
if (_isMounted.current) setVideoLink(data.response.url); };
var goToNext = function () {
playerRef.current.seekTo(0, "seconds");
setVideoLink(null);
if (playerInfo.next_id) {
params.id = playerInfo.next_id;
showVideo();
} else navigate(-1);};
<ReactPlayer
url={videoLink}
playing={isPlaying}
ref={playerRef}
key={params.id}
onProgress={() => {
current();
}}
config={{
file: {
forceHLS: true,
},
}}
/>

I would suggest you build your own player from scratch using just react and a style library.
I had similar issues using react-player and I had to resort to building my own custom player in which I could now ensure that buffering is handled the way I expect it to.
I handled buffering using the progress event as follows
const onProgress = () => {
if (!element.buffered) return;
const bufferedEnd = element.buffered.end(element.buffered.length - 1);
const duration = element.duration;
if (bufferRef && duration > 0) {
bufferRef.current!.style.width = (bufferedEnd / duration) * 100 + "%";
}
};
element.buffered represents a collection of buffered time ranges.
element.buffered.end(element.buffered.length - 1) gets the time at the end of the buffer range. With this value, I was able to compute the current buffer range and update the buffer progress accordingly.
I ended up writing an article that would help others learn to build an easily customizable player from scratch using just React and any style library (in this case charkra UI was used).

Related

React 17 useMemo create a losted instance of object

[EDIT]
Thanks to #dbuchet for his anwser. The problem comes from the fact that in StrictMode in dev mode only, everythings is running twice.
[PROBLEM]
I just got a weird beavour in React.
My context :
I want to create an EventSource for work with Mercure. So i created a hook and this hook store my EventSource with a useMemo. Until here everything's is fine. But after some tests, eachtime i call my hook, two connections are created not only one.
The problem is that i am not able to close the first connection. Only the second. This cause memory leak and undesired persistent connections.
My useMemo for the EventSource :
const eventSource = useMemo(() => {
if (topics.length === 0 || !hubUrl || !baseUrl) {
return null;
}
let url = new URL(hubUrl);
topics.forEach(topic => {
url.searchParams.append('topic', (baseUrl ? baseUrl : '') + topic);
});
return new EventSource(url, {
withCredentials: withCredentials
});
}, [baseUrl, hubUrl, withCredentials, topicsHash]);
After some investigations, i can see that if i add some Math.random(), logs and a simple setInterval, i am able to see that two different objects are created :
const eventSource = useMemo(() => {
if (topics.length === 0 || !hubUrl || !baseUrl) {
return null;
}
let url = new URL(hubUrl);
topics.forEach(topic => {
url.searchParams.append('topic', (baseUrl ? baseUrl : '') + topic);
});
console.log('opening... ' + temp)
let connectionInterval = null;
connectionInterval = setInterval(() => {
console.log('------------- setInterval ---------------')
onsole.log(topics, eventSource, temp);
clearInterval(connectionInterval);
});
let test = { test: temp, eventSource: new EventSource(url, {
withCredentials: withCredentials
})}
return test;
}, [baseUrl, hubUrl, withCredentials, topicsHash]);
The result in the logger is :
So we can see that my useMemo seems to be called only once (due to the fact that 'opening...' log is visible only once). But the random is different of the final object that i have at the end.
Moreover, setInterval is executed twice with both randoms visible.
Maybe something in my project cause this, so i created a simple peice of React and try the code below and same beavour :
const temp = useMemo(() => {
let test = Math.random();
let inter = setInterval(() => {
console.log('setInterval');
console.log(test);
clearInterval(inter);
});
console.log(test);
}, ['for block reloading'])
Result of the console :
Anyone got the same problem ?
Is there a way to avoid this, maybe i made a mistake somewhere or i misunderstood something with useMemo ?
Thanks !

Progress bar with WEB API

[First of all I need to tell that I'm a beginner in ReactJS]
There is a web API that I'm using for creating employee data. With that API, I'm sending employee data array.
http://localhost:5000/api/services/app/Employee/AddEmployees
Input json looks a like,
[{
"id": 5495,
"employeeName": "Sarwen",
"department": "Production",
"factoryLocation": "Denmark"
},
{
"id": 5496,
"employeeName": "Kate",
"department": "HR",
"factoryLocation": "Denmark"
}
.
.
.
.
.
.
.]
There are thousands of data in above json.
The web API got all the data at once and then process inside the server. It works totally fine.
public async Task<bool> AddEmployees(InputDto input)
{
//inserting to DB
}
But it took a little time to process. The issue is, the front end user doesn't know the status of that process. (Front end user have no idea, how many records remaining in the process).
I'm developing a React js application for doing above. This is how I send data to API.
async function saveEmployees(props) {
var data = {
Token : usr
EmpData: props//props will get the employee data set
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
};
const response = await fetch('http://localhost:5000/api/services/app/Employee/AddEmployees', requestOptions);
};
I need to show a kind of progress bar or a percentage. I checked for few progress bar options in ReactJS and it needs a percentage for showing the progress bar. Can any expert help me out of this for showing a progress bar regarding my scenario? Need to get current running (real time) record index or whatever progress to the front end.
You have a few options, which one works best depends on the back end implementation.
Approach 1: Split data in parts, send multiple requests
Unless the data needs to all be submitted at the same time, this is probably the easiest and best option. As your code already loads the data in memory in JS, there's not much code needed to turn it into a loop. Then you can just update the progress bar every time a part finishes.
If you use state, you can just pass the setter of the variable holding the done percentage as an argument to your async function, it's always the same function.
async function uploadBatched (setProgress) {
for (let i = 0; i < items.length; i+=batchSize) {
const chunk = items.slice(i, i + batchSize);
await saveEmployees(chunk);
setProgress(100 * i / items.length )
}
}
The code in the snippet below is a bit different/mangled because SO snippets can't have React and async together :( I did a hack with .then() that should have the same behavior as an async loop.
const {useState, useEffect} = React;
// Generate example data.
const data = Array.from(Array(1000).keys());
const batchSize = 100;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function saveEmployees(items) {
return sleep(10 * items.length);
};
function uploadBatched(items, setProgress) {
let previous;
console.log(items);
for (let i = 0; i < items.length; i+=batchSize) {
// Workaround code, please read the async version of uploadBatched instead.
// I also cheat and add batchSize, to avoid needing a complex fix for the last batch.
const doBatch = () => {
setProgress(100 * (i+batchSize) / items.length )
const chunk = items.slice(i, i + batchSize);
console.log();
return saveEmployees(chunk);
}
if (previous) {
previous = previous.then(doBatch)
} else {
previous = doBatch();
}
}
}
function App() {
const [progress, setProgress] = useState(0);
return <div>
<button onClick={()=>{uploadBatched(data,setProgress)}}>Upload</button>
<div className="progress-container">
<div style={{width: `${progress}%`}} className="progress">{progress}%</div>
</div>
</div>
}
const root = document.getElementById('root');
ReactDOM.render(<App/>, root);
.progress-container {
border: 1px solid black;
position: relative;
width: 240px;
height: 42px;
}
.progress {
height: 100%;
background: lightgreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Approach 2: Server side jobs + pulling status
You could send a request and create an asynchronous job to process the data, instead of letting the request wait for a response. The server just acknowledges it has received the request and will asynchronously process it.
Then you can poll a server endpoint that gives the current processing status and set the scrollbar size with it.
From the front end POV this is not harder than the first approach, but the back end infrastructure needs to be there.

Using progress handler when uploading files to AWS S3 with React

I am only recently dealing with the AWS SDK and thus please excuse if my approach is complete nonsense.
I want to upload a simple media file to my S3. I was following this tutorial and so far I am able to upload files without a problem. For userbility a progress bar would be a nice extra and therefore I was researching how to achieve this. I quickly found that the current AWS SDK v3 does not support httpUploadProgress anymore but we should use #aws-sdk/lib-storage instead. Using this library, I am still able to upload files to the S3 but I can't get the progress tracker to work! I assume this has something to do with me not fully understanding how to deal with async within a React component.
So here is my minified component example (I am using Chakra UI here)
const TestAWS: React.FC = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [progr, setProgr] = useState<number>();
const region = "eu-west-1";
const bucketname = "upload-test";
const handleClick = async () => {
inputRef.current?.click();
};
const handleChange = (e: any) => {
console.log('Start file upload');
const file = e.target.files[0];
const target = {
Bucket: bucketname,
Key: `jobs/${file.name}`,
Body: file,
};
const s3 = new S3Client({
region: region,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: region }),
identityPoolId: "---MY ID---",
}),
});
const upload = new Upload({
client: s3,
params: target,
});
const t = upload.on("httpUploadProgress", progress => {
console.log("Progress", progress);
if (progress.loaded && progress.total) {
console.log("loaded/total", progress.loaded, progress.total);
setProgr(Math.round((progress.loaded / progress.total) * 100)); // I was expecting this line to be sufficient for updating my component
}
});
await upload.done().then(r => console.log(r));
};
console.log('Progress', progr);
return (
<InputGroup onClick={handleClick}>
<input ref={inputRef} type={"file"} multiple={false} hidden accept='video/*' onChange={e => handleChange(e)} />
<Flex layerStyle='uploadField'>
<Center w='100%'>
<VStack>
<PlusIcon />
<Text>Choose Video File</Text>
</VStack>
</Center>
</Flex>
{progr && <Progress value={progr} />}
</InputGroup>
);
};
export default TestAWS;
So basically I see the event getting fired (start file upload). Then it takes a while and I see the Promise result and the Progress, 100 in my console. This means to me that the state variable gets updated (at least once) but the component does not re-render?
What is it what I am doing wrong here? Any help appreciated!
Alright, I have found the solution. The callback on the state variable works fine and does what it should. But the configuration of the Upload object was off. After digging into the source I found out that the event listener only gets triggered if the uploader has uploaded more data. Because Uploader chunks the uploads you have two separate config parameters which allow you to split your upload into separate chunks. So
const upload = new Upload({
client: s3,
params: target,
queueSize: 4, // 4 is minimum
partSize: 5*1024*1024 // 5MB is minimum
});
basically does the job when the file we upload is larger than 5MB! Only then the event gets triggered again and updates the state variable.
Since this uploader is made for handling large file uploads, this totally makes sense and we could simply adjust queueSize and partSize according to the file we want to upload. Something like
let queueSize = 10;
const file = event.target.files[0];
let partSize = file.size / (10 * 1024 * 1024); // 1/10th of the file size in MB
const upload = new Upload({
client: s3,
params: target,
queueSize: partSize > 5 queueSize : undefined,
partSize: partSize > 5 ? partsize : undefined
});
Obviously, this can be done much more sophisticated but I did not want to spend too much time on this since it is not part of the original question.
Conclusion
If your file is large enough (>5MB), you will see progress update, depending on the number of chunks (of 5MB or more) you have chosen to split your file.
Since this only affects the handleChange method from the original example, I post this for completeness
const handleChange = async ( event ) => {
const file = event.target.files[0]
const target = {
Bucket: 'some-S3-bucket',
Key: `jobs/${file.name}`,
Body: file,
};
const s3 = new S3Client({
region: 'your-region',
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: 'your-region' }),
identityPoolId: "your-id",
}),
});
// this will default to queueSize=4 and partSize=5MB
const upload = new Upload({
client: s3,
params: target
});
upload.on("httpUploadProgress", progress => {
console.log('Current Progress', progress);
setProgr(progress);
});
await upload.done().then(r => console.log(r));
}
Maybe this helps someone who has the same problem.
I came across your answer after having exactly the same problem (with Vue) today!
Indeed you are right: the AWS SDK JS v3 event only fires per part which is not at all clear and I wasted time debugging that too. Like for a 4MB file, it would only ever fire at 100%.
As you say, you can experiment with the part size but the minimum is 5MB and so on a slow connection I found it can appear that an upload is stuck as you have to wait for 5MB to get any data. Hmm. So what I did was look at the size of the file being uploaded. And if it is under a threshold (say 25MB, or whatever is applicable), well it's probably safe to upload that all in one go as you don't really need multipart uploading. And so I also made a presigned URL (https://aws.amazon.com/blogs/developer/generate-presigned-url-modular-aws-sdk-javascript/) which can be used to PUT using axios (since fetch does not support progress events yet).
So that way you can use upload for large files (where you actually need multipart uploading and where 5MB as a percentage of the file size is small), and use a presigned URL for small files and so get much more frequent updates.
The same progress event handler can be used by both.
this.$axios
.request({
method: "PUT",
url: SIGNED-URL-HERE,
data: file,
timeout: 3600 * 1000,
onUploadProgress: this.uploadProgress,
})
.then((data) => {
console.log("Success", data);
})
.catch((error) => {
console.log("Error", error.code, error.message);
});
Not ideal but it helps.

Have to run function 2 times for setting value to state in react?

Code:-
const [TimeStampsFromFile, setTimeStampsFromFile] = useState([])
const FilePicker = async () => {
var RNFS = require('react-native-fs')
// Pick a single file
try {
const res = await DocumentPicker.pick({
type: [DocumentPicker.types.plainText],
})
const filepath =
RNFS.ExternalStorageDirectoryPath + '/' + 'TimeStamps' + '/' + res.name
const file = await RNFS.readFile(filepath)
setTimeStampsFromFile(await file.split('\n'))
console.log('file data' + TimeStampsFromFile)
} catch (err) {
if (DocumentPicker.isCancel(err)) {
// User cancelled the picker, exit any dialogs or menus and move on
} else {
throw err
}
}
}
this function runs on pressing a button in my react native app:-
On pressing the first time, it needs to return value but it simply returns nothing. But on pressing the second time it returns the value. (Why is that??) or Am I making some mistakes in code??
The same thing is also happing with my other button too.
I found this answer : - hooks not set state at first time but not able to get solution of my problem (if you can help from this answer)
TimeStampsFromFile is empty the first time you log it because setTimeStampsFromFile is async. If you want to log TimeStampsFromFile when value will be updated you could use useEffect hook like:
useEffect(() => {
console.log(TimeStampsFromFile);
}, [TimeStampsFromFile]);
In this way, every time TimeStampsFromFile changes his value, useEffect will be called and log will show current value.
If you need to get the value of the TimeStampsFromFile from FilePicker function, is not necessary to get this value from TimeStampsFromFile. You could do something like:
...
const splitFile = await file.split('\n');
setTimeStampsFromFile(splitFile)
console.log('file data' + splitFile)
...

Perform Asynchronous Decorations in DraftJS?

I'm trying to perform real-time Named Entity Recognition highlighting in a WYSIWYG editor, which requires me to make a request to my back-end in between each keystroke.
After spending about a week on ProseMirror I gave up on it and decided to try DraftJS. I have searched the repository and docs and haven't found any asynchronous examples using Decorations. (There are some examples with Entities, but they seem like a bad fit for my problem.)
Here is the stripped down Codepen of what I'd like to solve.
It boils down to me wanting to do something like this:
const handleStrategy = (contentBlock, callback, contentState) => {
const text = contentBlock.getText();
let matchArr, start;
while ((matchArr = properNouns.exec(text)) !== null) {
start = matchArr.index;
setTimeout(() => {
// THROWS ERROR: Cannot read property '0' of null
callback(start, start + matchArr[0].length);
}, 200) // to simulate API request
}
};
I expected it to asynchronously call the callback once the timeout resolved but instead matchArr is empty, which just confuses me.
Any help is appreciated!
ok, one possible solution, a example, simple version (may not be 100% solid) :
write a function take editor's string, send it to server, and resolve the data get from server, you need to figure out send the whole editor string or just one word
getServerResult = data => new Promise((resolve, reject) => {
...
fetch(link, {
method: 'POST',
headers: {
...
},
// figure what to send here
body: this.state.editorState.getCurrentContent().getPlainText(),
})
.then(res => resolve(res))
.catch(reject);
});
determine when to call the getServerResult function(i.e when to send string to server and get entity data), from what I understand from your comment, when user hit spacebar key, send the word before to server, this can done by draftjs Key Bindings or react SyntheticEvent. You will need to handle case what if user hit spacebar many times continuously.
function myKeyBindingFn(e: SyntheticKeyboardEvent): string {
if (e.keyCode === 32) {
return 'send-server';
}
return getDefaultKeyBinding(e);
}
async handleKeyCommand(command: string): DraftHandleValue {
if (command === 'send-server') {
// you need to manually add a space char to the editorState
// and get result from server
...
// entity data get from server
const result = await getServerResult()
return 'handled';
}
return 'not-handled';
}
add entity data get from server to specific word using ContentState.createEntity()
async handleKeyCommand(command: string): DraftHandleValue {
if (command === 'send-server') {
// you need to manually add a space char to the editorState
// and get result from server
...
// entity data get from server
const result = await getServerResult()
const newContentState = ContentState.createEntity(
type: 'string',
mutability: ...
data: result
)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
// you need to figure out the selectionState, selectionState mean add
// the entity data to where
const contentStateWithEntity = Modifier.applyEntity(
newContentState,
selectionState,
entityKey
);
// create a new EditorState and use this.setState()
const newEditorState = EditorState.push(
...
contentState: contentStateWithEntity
)
this.setState({
editorState: newEditorState
})
return 'handled';
}
return 'not-handled';
}
create different decorators find words with specific entity data, and return different style or whatever you need to return
...
const compositeDecorator = new CompositeDecorator([
strategy: findSubjStrategy,
component: HandleSubjSpan,
])
function findSubjStrategy(contentBlock, callback, contentState) {
// search whole editor content find words with subj entity data
// if the word's entity data === 'Subj'
// pass the start index & end index of the word to callback
...
if(...) {
...
callback(startIndex, endIndex);
}
}
// this function handle what if findSubjStrategy() find any word with subj
// entity data
const HandleSubjSpan = (props) => {
// if the word with subj entity data, it font color become red
return <span {...props} style={{ color: 'red' }}>{props.children}</span>;
};

Resources