I have this implementation where the backend strams audio files. And I want an approach that enables fast loading and playing of the audio file : Everything works as intended but there seem to be erratic behaviour where it takes very long time to load on my web page:
I am new to React Js .
import React, { useState,useEffect, useRef } from 'react';
import { Icon } from '@iconify/react';
import { podcastIcons } from '@/db/data';
import axios from 'axios';
import WaveSurfer from 'wavesurfer.js';
const PlayFile: React.FC = () => {
const [linkName, setLinkName] = useState<string>(''); // State for link name
const [audioURL, setAudioURL] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const waveformRef = useRef<HTMLDivElement | null>(null); // Reference to the waveform container
const waveSurfer = useRef<WaveSurfer | null>(null); // Reference to the WaveSurfer instance
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const formData = new FormData();
formData.append('link_name', linkName);
try {
const response = await axios.post('http://localhost:8000/submitform/', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
const audioBlob = new Blob([response.data], { type: 'audio/mpeg' });
const audioUrl = URL.createObjectURL(audioBlob);
setAudioURL(audioUrl); // Set audio UR
// Initialize Wavesurfer after the audio is set
console.log('checking waveform');
if (waveformRef.current && waveSurfer.current === null) {
waveSurfer.current = WaveSurfer.create({
container: waveformRef.current,
waveColor: '#ddd',
progressColor: '#333',
cursorColor: '#333',
height: 64,
barWidth: 1,
responsive: true,
// backend: 'MediaElement'
});
}
// Load the audio file into Wavesurfer
console.log('after checking ...');
if (waveSurfer.current && audioUrl) {
console.log('Waveform loaded with audio URL:', audioUrl);
waveSurfer.current.load(audioUrl);
}
// } catch (error) {
// console.error('Error generating the podcast:', error);
// }
} catch (error) {
console.error('Error submitting the form', error);
}finally {
setIsLoading(false);
}
};
);
return (
<form onSubmit={handleSubmit} className=" bg-gradient-to-r from-emerald-100 to-lime-200 flex text-black flex-col lg:flex-row lg:space-x-8 p-6 font-sans py-12 px-6">
</div>
{/* Link Name Input Field */}
<div className="mb-6">
<label className="font-semibold mb-2">Document Link</label>
<input
type="Document link"
value={linkName}
onChange={(e) => setLinkName(e.target.value)}
className="border rounded w-full py-2 px-3"
placeholder="Link to Document"
/>
</div>
<button className="bg-black text-white py-3 px-8 rounded w-full">
Create podcast
</button>
{/* Show the audio player and waveform if the audio URL is available */}
{audioURL && (
<div className="mt-4">
<div className="waveform" ref={waveformRef}></div> {/* Waveform container */}
<div className="mt-2 flex gap-4">
<button onClick={() => waveSurfer.current?.playPause()} className="bg-green-500 text-white py-2 px-4 rounded">
{waveSurfer.current?.isPlaying() ? 'Pause' : 'Play'}
</button>
<button onClick={() => waveSurfer.current?.stop()} className="bg-red-500 text-white py-2 px-4 rounded">
Stop
</button>
</div>
</div>
)}
</div>
</form>
);
};
export default PlayFile;
I have tried the code above and it works sometimes and other times it does not. I am looking for wats of optimizing the code and gaining hands-on-experience in handling audio rendering on frontnd in a scalable way. Examples illustrating best practices will be well appreciated. SOlution using peaks.js, howler and other similar libraries that are optimized for performance is also welcomed.
Adopting Howler.js in your React application offers numerous advantages that directly address the challenges you’re facing with your current implementation:
By leveraging Howler.js, you can create a scalable, efficient, and user-friendly audio playback feature in your React application, resolving the erratic loading behaviors and enhancing overall performance.
Try this code:
If you later decide to incorporate advanced features like waveform visualization, you can integrate specialized libraries (e.g., WaveSurfer.js) alongside Howler.js, maintaining a modular and efficient architecture.
import React, { useState, useRef, useEffect } from 'react';
import axios from 'axios';
import { Howl } from 'howler';
const PlayFileWithHowler = () => {
const [linkName, setLinkName] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [howl, setHowl] = useState(null);
const [isPlaying, setIsPlaying] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setIsLoading(true);
const formData = new FormData();
formData.append('link_name', linkName);
try {
const response = await axios.post('http://localhost:8000/submitform/', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
responseType: 'blob', // Ensure the response is a blob
});
const audioBlob = new Blob([response.data], { type: 'audio/mpeg' });
const audioUrl = URL.createObjectURL(audioBlob);
const newHowl = new Howl({
src: [audioUrl],
html5: true, // Use HTML5 Audio to enable streaming large files
onend: () => setIsPlaying(false),
onplay: () => setIsPlaying(true),
onpause: () => setIsPlaying(false),
onstop: () => setIsPlaying(false),
onloaderror: (id, error) => console.error('Load error:', error),
onplayerror: (id, error) => console.error('Play error:', error),
});
setHowl(newHowl);
} catch (error) {
console.error('Error submitting the form', error);
// Optionally, set an error state here to display to the user
} finally {
setIsLoading(false);
}
};
const handlePlayPause = () => {
if (howl) {
howl.playing() ? howl.pause() : howl.play();
}
};
const handleStop = () => {
if (howl) {
howl.stop();
}
};
// Cleanup Howl on unmount
useEffect(() => {
return () => {
if (howl) {
howl.unload();
}
};
}, [howl]);
return (
<form onSubmit={handleSubmit} className="bg-gradient-to-r from-emerald-100 to-lime-200 flex flex-col lg:flex-row lg:space-x-8 p-6 font-sans py-12 px-6">
{/* Link Name Input Field */}
<div className="mb-6 flex-1">
<label className="font-semibold mb-2 block">Document Link</label>
<input
type="text"
value={linkName}
onChange={(e) => setLinkName(e.target.value)}
className="border rounded w-full py-2 px-3"
placeholder="Link to Document"
required
/>
</div>
<div className="flex items-end">
<button
type="submit"
className="bg-black text-white py-3 px-8 rounded w-full"
disabled={isLoading}
>
{isLoading ? 'Loading...' : 'Create Podcast'}
</button>
</div>
{/* Show the audio controls if Howl is initialized */}
{howl && (
<div className="mt-4 w-full">
<div className="flex gap-4">
<button
type="button"
onClick={handlePlayPause}
className="bg-green-500 text-white py-2 px-4 rounded"
>
{isPlaying ? 'Pause' : 'Play'}
</button>
<button
type="button"
onClick={handleStop}
className="bg-red-500 text-white py-2 px-4 rounded"
>
Stop
</button>
</div>
</div>
)}
</form>
);
};
export default PlayFileWithHowler;