1

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.

1 Answer 1

0

Adopting Howler.js in your React application offers numerous advantages that directly address the challenges you’re facing with your current implementation:

  1. Performance Improvements: Efficient handling of large audio files through streaming reduces load times and memory usage.
  2. Simpler and More Reliable Code: A straightforward API minimizes complexity, making your application more maintainable and less prone to bugs.
  3. Better User Experience: Faster playback initiation and responsive controls enhance the overall user experience.
  4. Robust Resource Management: Proper cleanup and error handling ensure your application remains stable and performant over time.
  5. Cross-Browser Consistency: Howler.js abstracts away browser inconsistencies, providing a uniform experience across different environments.

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;
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, I also went with howler. Could you recommend how to include wave form in howler?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.