Search code examples
javascripthtmlflaskbuttonaudio

Getting random wav from flask and exchange src in audio


basic situation: Starting from a HTML page with 3 buttons for 3 different types of sounds (sound0 (e.g. denial sound), sound1 (e.g. approval sound), sound2(e.g. laughing)), i want on "page load" to POST the file type to the Flask server, which should dynamically search for an individual instance of this sound type and return this path (this behavior is currently hard coded, since first the communication should work). I can receive this url in javascript, however this returns (in javascript) in the error of "Failed to load resource: the server responded with a status of 404 (NOT FOUND)". Actually on receiving the path, the audio source should be exchanged. After doing this for all 3 buttons, a playback loop (iterating over every 3 files) should be triggered by playing sound0. The files are currently on the server in app/static/soundfiles/SOME_DEEPER_PATH_TO_CERTAIN_WAV.wav. I currently don´t understand, whats going wrong and i would be very happy to get help with this issue. My code so far:

button_page.html: (located in app/templates/button_page.html)

<!DOCTYPE html>
<html>
<head>
    <style>

        .button-row {
            display: flex;
            justify-content: center;
            margin-top: 30px;

        }

        .question {
            font-weight: bold;
            font-size: 20px;
            margin-bottom: 10px;
            color: white; /* Set the text color to white */
        }

        .error{
            color: red;
            display: none;
            margin-top: 5px;
        }

        .end_button {
            position: relative;
            bottom: -80px;
            margin-bottom: 100px;
            left: 125%;
            transform: translateX(-50%);
        }

        .button {
          min-width: 150px;
              min-height: 50px;
              font-family: 'Nunito', sans-serif;
              font-size: 16px;
              text-transform: uppercase;
              letter-spacing: 1.3px;
              font-weight: 700;
              color: #313133;
              background: #E592F6;
              z-index: 2;
              background: linear-gradient(90deg, rgba(229,146,246,1) 0%, rgb(196, 119, 213) 100%);
              border: none;
              border-radius: 1000px;
              box-shadow: 12px 12px 24px rgba(229,146,246,.64);
              transition: all 0.3s ease-in-out 0s;
              cursor: pointer;
              outline: none;
              position: relative;
              padding: 10px;
        }

        .wrapper_loading{
            width:200px;
            height:60px;
            position: absolute;
            left:50%;
            margin-top: 150px;
            transform: translate(-50%, -50%);
            visibility: hidden;
        }
        .circle{
            width:20px;
            height:20px;
            position: absolute;
            border-radius: 50%;
            background-color: #E592F6;
            left:15%;
            transform-origin: 50%;
            animation: circle .5s alternate infinite ease;
        }

        @keyframes circle{
            0%{
                top:60px;
                height:5px;
                border-radius: 50px 50px 25px 25px;
                transform: scaleX(1.7);
            }
            40%{
                height:20px;
                border-radius: 50%;
                transform: scaleX(1);
            }
            100%{
                top:0%;
            }
        }
        .circle:nth-child(2){
            left:45%;
            animation-delay: .2s;
        }
        .circle:nth-child(3){
            left:auto;
            right:15%;
            animation-delay: .3s;
        }
        .shadow{
            width:20px;
            height:4px;
            border-radius: 50%;
            background-color: rgba(148, 98, 160, 0.5);
            position: absolute;
            top:62px;
            transform-origin: 50%;
            z-index: -1;
            left:15%;
            filter: blur(1px);
            animation: shadow .5s alternate infinite ease;
        }

        @keyframes shadow{
            0%{
                transform: scaleX(1.5);
            }
            40%{
                transform: scaleX(1);
                opacity: .7;
            }
            100%{
                transform: scaleX(.2);
                opacity: .4;
            }
        }
        .shadow:nth-child(4){
            left: 45%;
            animation-delay: .2s
        }
        .shadow:nth-child(5){
            left:auto;
            right:15%;
            animation-delay: .3s;
        }
        .wrapper_loading span{
            position: absolute;
            top:75px;
            bottom:75px;
            font-family: 'Nunito', sans-serif;
            font-size: 20px;
            letter-spacing: 12px;
            color: #E592F6;;
            left:15%;
        }

        .submit-wrapper {
          display: grid;
          grid-template-columns: repeat(10, 1fr);
          gap: 10px;
          grid-auto-rows: minmax(100px, auto);
        }

        .wrapper {
          display: grid;
          grid-template-columns: repeat(5, 1fr);
          gap: 10px;
          grid-auto-rows: minmax(100px, auto);
        }
    </style>
</head>
<body>

<audio id="voice0" onended="playVoice1()" onpause="stop_ring_0()">
</audio><audio id="voice1" onended="playVoice2()" onpause="stop_ring_1()"></audio>
<audio id="voice2" onended="playVoice1()" onpause="stop_ring_2()"></audio>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="{{url_for('static', filename='js/js_tmp.js')}}"></script>

<div class="wrap">
    <hr class="top-hr">
    <div class="wrapper_loading" id="wrapper_loading_id">
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="circle"></div>
        <div class="shadow"></div>
        <div class="shadow"></div>
        <div class="shadow"></div>
        <!-- <span class="synthesizing_loader_text">Loading</span> -->
    </div>
</div>
<div class="button-row" id="button-container"></div>
    <div class="button-row">
        <button id="button-id1" class="button" onclick="playSoundType1()">Sound Type1</button>
        <button id="button-id2" class="button" onclick="playSoundType2()">Sound Type2</button>
        <button id="button-id3" class="button" onclick="playSoundType3()">Sound Type3</button>
    </div>
<script>
        init_button_sounds();
</script>
</body>
</html>

js_tmp.js located in static/js/js_tmp.js:


function init_button_sounds() {

    init_button_sound("voice0")
    init_button_sound("voice1")
    init_button_sound("voice2")
    playSoundType1();
}


function init_button_sound(audio_id, rd) {
    var route = '/getaudio'.concat(audio_id.slice(-1))

    var rd = String(Date.now())
    $.post(route).done(
        function(response) {
        var audio_path=response['audioFilePath']
        var audio_element=document.getElementById(audio_id);

        // assemble source.src & attach to audio
        var source = document.createElement('source');
        source.src = audio_path;
        // Append the source to the audio element
        audio_element.appendChild(source);

        // Load the new audio source
        document.getElementById(audio_id).load();

        }
    ).fail(
        function() {
            alert("Something failed. Please load again.");
        }
    );
}




function playSoundType1(){
    stopAllVoices()
    playVoice0()
}

function playSoundType2(){
    stopAllVoices()
    playVoice1()
}


function playSoundType3(){
    stopAllVoices()
    playVoice2()
}


function stopAllVoices(){
    stop_ring_0()
    stop_ring_1()
    stop_ring_2()
    document.getElementById('voice0').pause();
    document.getElementById('voice1').pause();
    document.getElementById('voice2').pause();
}


function playVoice0(){
  document.getElementById('voice0').play();
  stop_ring_1()
  stop_ring_2()
  start_ring_0()
}

function playVoice2(){
  document.getElementById('voice2').play();
  stop_ring_1()
  stop_ring_0()
  start_ring_2()
}



function playVoice1(){
  document.getElementById('voice1').play();
  stop_ring_2()
  stop_ring_0()
  start_ring_1()
}

function stop_ring_0(){
  var styleElem = document.head.appendChild(document.createElement("style"));
  styleElem.innerHTML = "#button-id1::after{animation:None;display:None;}";
}

function stop_ring_1(){
  var styleElem = document.head.appendChild(document.createElement("style"));
  styleElem.innerHTML = "#button-id2::after{animation:None;display:None;}";
}



function stop_ring_2(){
  var styleElem = document.head.appendChild(document.createElement("style"));
  styleElem.innerHTML = "#button-id3::after{animation:None;display:None;}";
}


function start_ring_0(){
  var styleElem2 = document.head.appendChild(document.createElement("style"));
  styleElem2.innerHTML = "#button-id1::after{    animation:ring 1.5s infinite;display: block;}";
}

function start_ring_1(){
  var styleElem2 = document.head.appendChild(document.createElement("style"));
  styleElem2.innerHTML = "#button-id2::after{    animation:ring 1.5s infinite;display: block;}";
}

function start_ring_2(){
  var styleElem2 = document.head.appendChild(document.createElement("style"));
  styleElem2.innerHTML = "#button-id3::after{    animation:ring 1.5s infinite;display: block;}";
}

routes.py in app/routes.py

@app.route('/')
def button_page():
    return render_template('button_page.html')

from pathlib import Path
sound1_path=Path('sound_files\\sound1\\a.wav')
sound2_path=Path('sound_files\\sound2\\b.wav')
sound3_path=Path('sound_files\\sound3\\c.wav')
@app.route('/getaudio<string:id>', methods=['GET','POST'])
def getaudio(id):
    from flask import url_for

    id_str = id
    sound_path=None

    if id_str=='0':
        sound_path=sound1_path
    elif id_str=='1':
        sound_path=sound2_path
    elif id_str=='2':
        sound_path=sound3_path

    audio_url = url_for('static', filename=str(sound_path))
    return(jsonify({'audioFilePath': str(audio_url) }))


def create_app():
    from flask import Flask
    app = Flask(__name__)
    app.secret_key = "random"
    return app

if __name__=="__main__":
    app=create_app()
    app.run(debug=True)

I tried playing around with different separators \ and / and also cutting the / before the static prefix. I really don´t know, where the error comes from


Solution

  • The separator to use should be /.
    Unfortunately, I was unable to reproduce your issue with the example given.

    Nevertheless, here is my example to load all sources and sounds and then be able to iterate over the sounds depending on the button pressed.

    app.py
    from flask import (
        Flask, 
        abort, 
        jsonify, 
        render_template, 
        url_for
    )
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        return render_template('index.html')
    
    @app.post('/sound-<int:id>')
    def sound(id):
        sounds = (
            'sound_files/sound1/a.wav', 
            'sound_files/sound2/b.wav', 
            'sound_files/sound3/c.wav', 
        )
        if 0 <= id < len(sounds):
            return jsonify(src=url_for('static', filename=sounds[id]))
        abort(404)
    
    templates/index.html
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Index</title>
        <style>
            #button-group {
                display: None;
            }
            .button-row {
                display: flex;
                justify-content: center;
                margin: 2rem;
                gap: 1rem;
            }
            .visible#button-group, .visible#spinner {
                display: block;
            }
            #spinner {
                position: absolute;
                bottom: 0;
                right: 0;
                margin: 4px;
                width: 32px;
                height: 32px;
                border: 3px solid #ccc;
                border-bottom-color: transparent;
                border-radius: 50%;
                display: None;
                box-sizing: border-box;
                animation: rotation 1s linear infinite;
            }
    
            @keyframes rotation {
                0% {
                    transform: rotate(0deg);
                }
                100% {
                    transform: rotate(360deg);
                }
            } 
        </style>
    </head>
    <body>
        <div id="spinner" class="visible"></div>
        <div id="button-group">
            <div class="button-row">
                <button>Sound Type1</button>
                <button>Sound Type2</button>
                <button>Sound Type3</button>
            </div>
        </div>
    
        <script type="text/javascript">
            (function() {
    
                // Load source using given id.
                const loadSource = (id) => {
                    return fetch(`/sound-${id}`, { method: 'post' })
                        .then(resp => {
                            if (!resp.ok) throw new Error('Something went wrong. (01)');
                            return resp.json();
                        })
                        .then(data => {
                            if (!data['src']) throw new Error('Something went wrong. (02)');
                            return data['src'];
                        });
                };
    
                // Load the sound with the given source and wait until it is playable. 
                const loadAudio = (src) => {
                    return new Promise((resolve, reject) => {
                        const audio = new Audio(src);
                        audio.preload = 'auto';
                        audio.addEventListener('canplaythrough', () => resolve(audio));
                        audio.addEventListener('error', reject);
                    });
                };
    
                const loadSounds = () => {
                    const soundIds = [0, 1, 2];
                    Promise.all(soundIds.map(loadSource)).then(async (srcs) => {
                        console.log('All sources retrieved.');
                        try {
    
                            // Load all sounds.
                            const sounds = await Promise.all(srcs.map(loadAudio));
                            console.log('All sounds loaded.');
    
                            // Register listeners to play next sound.
                            let current = 0, counter = 0;
                            sounds.forEach(snd => {
                                snd.addEventListener('ended', () => {
                                    if (++counter >= sounds.length) return;
                                    sounds[++current % sounds.length].play();
                                });
                            });
    
                            // Register all click listeners.
                            document.querySelectorAll('#button-group button').forEach((btn, i) => {
                                btn.addEventListener('click', () => {
                                    // Stop and reset all sounds.
                                    sounds.forEach(snd => {
                                        snd.pause();
                                        snd.currentTime = 0;
                                    });
                                    // Play sound.
                                    counter = 0;
                                    sounds[current = i].play();
                                });
                            });
    
                            // Show all buttons as soon as all sounds are playable.
                            const buttonGroup = document.getElementById('button-group');
                            buttonGroup && buttonGroup.classList.toggle('visible');
    
                            // Hide the loading indicator.
                            const spinner = document.getElementById('spinner');
                            spinner && spinner.classList.toggle('visible');
    
                        } catch(err) {
                            console.error('Something went wrong. (03)');
                        }
                    }).catch(console.error);
                };
    
                loadSounds();
            })();
        </script>
    </body>
    </html>