利用recorder.js+XMLHttpRequest+flask, post(upload) 网页录音传回服务器的问题

问题描述:
利用recorder.js+XMLHttpRequest+flask post(upload) 网页录音 to server,
已实现在线录音,期望利用XMLHttpRequest把录音传回服务器,然后保存到服务器本地。
但是把录音post 到服务器出现400错误(BAD REQUEST)

The browser (or proxy) sent a request that this server could not understand.

)

程序部分

flask app.py

from flask import Flask, render_template,request
import os

app=Flask(__name__)

@app.route('/', methods=['POST','GET'])
def post_to_server():
    if request.method =="POST":
        f = request.form['audio_data']
        f.save(os.path.join("uploads", f.filename))
        return render_template('index.html')
    else:
        return render_template('index.html')



if __name__=='__main__':
    # app.run(host="0.0.0.0", port=7003)
    app.run(port=7005)


index.html部分
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Simple WebAudioRecorder.js demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='css/style.css') }}">
  </head>
  <body>
	<p>Convert recorded audio to:<br>
	<select id="encodingTypeSelect">
	  <option value="wav">Waveform Audio (.wav)</option>
	  <option value="mp3">MP3 (MPEG-1 Audio Layer III) (.mp3)</option>
	  <option value="ogg">Ogg Vorbis (.ogg)</option>
	</select>
	</p>
	<div id="controls">
		<button id="recordButton">Record</button>
		<button id="stopButton" disabled>Stop</button>
	</div>
	<div id="formats"></div>
	<h3>Log</h3>
	<pre id="log"></pre>

	<h3>Recordings</h3>
	<ol id="recordingsList"></ol>

  	<!-- inserting these scripts at the end to be able to use all the elements in the DOM -->
	<script src="{{ url_for('static',filename='js/WebAudioRecorder.min.js') }}"></script>
	<script src="{{ url_for('static',filename='js/app.js') }}"></script>
  </body>
</html>

app.js部分
//webkitURL is deprecated but nevertheless
URL = window.URL || window.webkitURL;

var gumStream; 						//stream from getUserMedia()
var recorder; 						//WebAudioRecorder object
var input; 							//MediaStreamAudioSourceNode  we'll be recording
var encodingType; 					//holds selected encoding for resulting audio (file)
var encodeAfterRecord = true;       // when to encode

// shim for AudioContext when it's not avb. 
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext; //new audio context to help us record

var encodingTypeSelect = document.getElementById("encodingTypeSelect");
var recordButton = document.getElementById("recordButton");
var stopButton = document.getElementById("stopButton");

//add events to those 2 buttons
recordButton.addEventListener("click", startRecording);
stopButton.addEventListener("click", stopRecording);

function startRecording() {
	console.log("startRecording() called");

	/*
		Simple constraints object, for more advanced features see
		https://addpipe.com/blog/audio-constraints-getusermedia/
	*/
    
    var constraints = { audio: true, video:false }

    /*
    	We're using the standard promise based getUserMedia() 
    	https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
	*/

	navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
		__log("getUserMedia() success, stream created, initializing WebAudioRecorder...");

		/*
			create an audio context after getUserMedia is called
			sampleRate might change after getUserMedia is called, like it does on macOS when recording through AirPods
			the sampleRate defaults to the one set in your OS for your playback device

		*/
		audioContext = new AudioContext();

		//update the format 
		document.getElementById("formats").innerHTML="Format: 2 channel "+encodingTypeSelect.options[encodingTypeSelect.selectedIndex].value+" @ "+audioContext.sampleRate/1000+"kHz"

		//assign to gumStream for later use
		gumStream = stream;
		
		/* use the stream */
		input = audioContext.createMediaStreamSource(stream);
		
		//stop the input from playing back through the speakers
		//input.connect(audioContext.destination)

		//get the encoding 
		encodingType = encodingTypeSelect.options[encodingTypeSelect.selectedIndex].value;
		
		//disable the encoding selector
		encodingTypeSelect.disabled = true;

		recorder = new WebAudioRecorder(input, {
		  workerDir: "static/js/", // must end with slash
		  encoding: encodingType,
		  numChannels:2, //2 is the default, mp3 encoding supports only 2
		  onEncoderLoading: function(recorder, encoding) {
		    // show "loading encoder..." display
		    __log("Loading "+encoding+" encoder...");
		  },
		  onEncoderLoaded: function(recorder, encoding) {
		    // hide "loading encoder..." display
		    __log(encoding+" encoder loaded");
		  }
		});

		recorder.onComplete = function(recorder, blob) { 
			__log("Encoding complete");
			createDownloadLink(blob,recorder.encoding);
			encodingTypeSelect.disabled = false;
		}

		recorder.setOptions({
		  timeLimit:120,
		  encodeAfterRecord:encodeAfterRecord,
	      ogg: {quality: 0.5},
	      mp3: {bitRate: 160}
	    });

		//start the recording process
		recorder.startRecording();

		 __log("Recording started");

	}).catch(function(err) {
	  	//enable the record button if getUSerMedia() fails
    	recordButton.disabled = false;
    	stopButton.disabled = true;

	});

	//disable the record button
    recordButton.disabled = true;
    stopButton.disabled = false;
}

function stopRecording() {
	console.log("stopRecording() called");
	
	//stop microphone access
	gumStream.getAudioTracks()[0].stop();

	//disable the stop button
	stopButton.disabled = true;
	recordButton.disabled = false;
	
	//tell the recorder to finish the recording (stop recording + encode the recorded audio)
	recorder.finishRecording();

	__log('Recording stopped');
}

function createDownloadLink(blob,encoding) {
	
	var url = URL.createObjectURL(blob);
	var au = document.createElement('audio');
	var li = document.createElement('li');
	var link = document.createElement('a');

    var filename = new Date().toISOString()

	//add controls to the <audio> element
	au.controls = true;
	au.src = url;

	//link the a element to the blob
	link.href = url;
	link.download = new Date().toISOString() + '.'+encoding;
	link.innerHTML = link.download;

	//add the new audio and a elements to the li element
	li.appendChild(au);
	li.appendChild(link);

    //upload link
	var upload = document.createElement('a');
	upload.href="#";
	upload.innerHTML = "Upload";
	upload.addEventListener("click", function(event){
		  var xhr=new XMLHttpRequest();
		  xhr.onload=function(e) {
		      if(this.readyState === 4) {
		          console.log("Server returned: ",e.target.responseText);
		      }
		  };
		  var fd=new FormData();
		  fd.append("audio_data",blob, filename);
		  xhr.open("POST","/",true);
		  xhr.send(fd);
	})
	li.appendChild(document.createTextNode (" "))//add a space in between
	li.appendChild(upload)//add the upload link to li

	//add the li element to the ol
	recordingsList.appendChild(li);

}



//helper function
function __log(e, data) {
	log.innerHTML += "\n" + e + " " + (data || '');
}

尝试把url改成http://localhost:7005,安装flask-CORS,
报以下的错误
flask 服务器方面 报错

  File "C:...\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "C:\...\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\...\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "...app.py", line 16, in post_to_server
    f = request.form['audio_data']
  File "...\lib\site-packages\werkzeug\datastructures.py", line 431, in __getitem__
    raise exceptions.BadRequestKeyError(key)
werkzeug.exceptions.HTTPException.wrap.<locals>.newcls: 400 Bad Request: KeyError: 'audio_data'

浏览器报错

:7005/#:1 Access to XMLHttpRequest at 'http://localhost:7005/' from origin 'http://127.0.0.1:7005' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
app.js:163 POST http://localhost:7005/ net::ERR_FAILED

程序部分修改
app.js部分

app.py部分

+ from flask_cors import CORS
...
+ CORS(app)

继续做以下修改,

xhr.open("POST","http://127.0.0.1:7005",true)

依然报以下错误

  File "...app.py", line 16, in post_to_server
    f = request.form['audio_data']
  File "...werkzeug\datastructures.py", line 431, in __getitem__
    raise exceptions.BadRequestKeyError(key)
werkzeug.exceptions.HTTPException.wrap.<locals>.newcls: 400 Bad Request: KeyError: 'audio_data'

我不太清楚 recorder.js 怎么用。看报错是在服务器端收到的请求里面没有录音数据,也就是没有 audio_data 这个字段,所以你需要看下客户端代码是不是正确的把录音数据加到 POST 请求里了(确保数据的键是 audio_data)?

主要是这段代码把录音添加到了audio_data字段里

var fd=new FormData();
		  fd.append("audio_data",blob, filename);
		  xhr.open("POST","/",true);
		  xhr.send(fd);

根据参考代码的提示,服务端,把flask改成php时运行是不报错的。

<?php
print_r($_FILES); //this will print out the received name, temp name, type, size, etc.
$size = $_FILES['audio_data']['size']; //the size in bytes
$input = $_FILES['audio_data']['tmp_name']; //temporary name that PHP gave to the uploaded file
$output = $_FILES['audio_data']['name'].".wav"; //letting the client control the filename is a rather bad idea
//move the file from temp name to local folder using $output name
move_uploaded_file($input, $output)
?>

打印 request.form 会输出什么?

不好意思,回复晚了。
我加了一行

    if request.method =="POST":
        print(request.form[0])

报错如下

  File "app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "simple-web-audio-recorder-demo\app.py", line 16, in post_to_server
    print(request.form[0])
  File "...\werkzeug\datastructures.py", line 431, in __getitem__
    raise exceptions.BadRequestKeyError(key)
werkzeug.exceptions.HTTPException.wrap.<locals>.newcls: 400 Bad Request: KeyError: '0'

没事。我的意思是直接打印 request.form:

print(request.form)

好的。另外这是js+php实现的参考代码https://www.google.co.jp/amp/s/blog.addpipe.com/using-recorder-js-to-capture-wav-audio-in-your-html5-web-site/amp/

直接打印print(request.form)结果:

127.0.0.1 - - [27/Aug/2019 05:55:58] "?[37mGET /static/js/WebAudioRecorder.min.js HTTP/1.1?[0m" 200 -
127.0.0.1 - - [27/Aug/2019 05:55:58] "?[37mGET /static/js/app.js HTTP/1.1?[0m" 200 -
127.0.0.1 - - [27/Aug/2019 05:55:58] "?[33mGET /favicon.ico HTTP/1.1?[0m" 404 -
127.0.0.1 - - [27/Aug/2019 05:56:05] "?[36mGET /static/js/WebAudioRecorderWav.min.js HTTP/1.1?[0m" 304 -
ImmutableMultiDict([])