近日在做毕设,需要搭建一个完整的平台,用来对外展示组里的语音相关模型(类似这种)。为了实现这种需求,最关键的是客户端(浏览器)和服务端(server)之间能正确地相互通信。如果你学过计算机网络的话,你会发现,承担这项任务的是HTTP request (浏览器-->服务器)和HTTP response (服务器-->浏览器)。对于后者,现在已经有很多成熟的开源后端框架(nginx+django)帮助你在服务器端接收request,处理request,然后生成response返回, 我就不做过多的介绍了,本文的目的主要是为了解决在客户端如何生成request的问题。
在客户端所看到的网页(html+css),实际上是静态的,他们只能用来展示页面,真正从页面中获取信息并制作request的是js文件。通过在html中插入javascript脚本,当某一事件发生时(比如点击某个button),就可以执行这个脚本 :
.html中对应按钮的部分
<div class="waves-effect waves-light btn" id="browse-container">
<span>Browse files</span>
<input type="file" id="browse-button" accept="audio/*">
</div>
<div>
<span>source audio</span>
<audio id="img-card" controls></audio><br>
</div>
对应的 .js部分
$("#browse-button").change(function() {
loadAudio($("#browse-button").prop("files")[0]);
});
这段的意思是:js通过id(browse-container)来定位html中的某个实体(input),当这个实体发生某种变化(.change)就会执行后面的function。具体到上面的需求中,function要做的就是1:找到音频数据,2:预加载到浏览器缓存中。
找到音频数据并预加载
在upload之前,浏览器必须先读取出本地音频文件的内容到内存中(此时还没开始上传服务器),具体代码如下:
loadAudio = function(file) {
var reader = new FileReader();
reader.onload = function(event) {
$('#img-card').attr('src', event.target.result);
};
reader.readAsDataURL(file);
switchCard(1);
};
实际上file就是前面的$("#browse-button").prop("files")[0] 他传给loadaudio的是音频文件在本地的地址,然后js会创建一个reader以base64字节的方式读取出文件所有的信息,注意此时会把读取的信息赋给上面html中的<audio>的src属性,(event.target.result是一个字符串"data:audio/wav;base64',GFYUGDYUISC...(音频数据的字节流)"此时便可以在浏览器中预听将要上传的音频,如下图所示(注意在loadaudio执行之前,由于src属性为空,audio块会是灰色的)
上传音频数据并显示下载的新音频
得到想上传的音频后,接下来要做的就是为它制作request并能接收服务器返回的response,在js中,ajax()可以完成这两项工作:
upload and detect audio按钮对应的.html
<div class="waves-effect waves-light btn" id="upload-button" >
<span>Upload and Detect Audio</span>
</div>
相应的.js
$('#upload-button').click(function() {
$('.modal').modal('open');
});
$('.modal').modal({
dismissible: false,
ready: function(modal, trigger) {
$.ajax({
type: "POST",
url:'VC_api/',
data: {
'image64': $('#img-card').attr('src')
},
dataType: 'text',
success: function(data) {
modal.modal('close');
loadStats(data);
},
error: function(data) {
modal.modal('close');
loadStats(data);
},
}).always(function() {
modal.modal('close');
});
}
});
首先js检测到upload-button出现‘点击’事件(此时browse-button的读取音频和预览工作已经完成),开始调用ajax构造request,这个request属于POST请求(因为要向服务器发送数据,如果只是从服务器请求数据,则用GET),data就是之前的<audio>块的src,url就是你请求的服务名。如果成功收到response就会进入success,进而提取出response中的音频数据,否则会进入error。.always是指ajax全部执行完毕后关闭modal(bootstrap中的一个子窗口,这里不需要过多深入)完成后视图:
完整代码
html
{% load staticfiles %}
<html>
<head>
<title>THU Audio</title>
<link rel="stylesheet" href="{% static 'css/classify.css' %}" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/css/materialize.min.css" />
<link href="https://fonts.googleapis.com/css?family=Noto+Sans" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/js/materialize.min.js"></script>
<script src="{% static 'js/classify.js' %}"></script>
</head>
<body>
<div class="container row main-container">
<div class="col s12 dd-container">
<div class="card dd-card">
<div class="card-content black-text file-field">
<div id="drop-container">
<i class="material-icons large upload-icon">cloud_upload</i>
<span class="drag-txt"><b>Drag and drop audio here</b></span>
<span class="or-txt">or</span>
<form action="#">
<div class="waves-effect waves-light btn" id="browse-container">
<span>Browse files</span>
<input type="file" id="browse-button" accept="audio/*">
</div>
</form>
</div>
</div>
</div>
</div>
<div class="col s12 uf-container">
<div class="card uf-card">
<span>source audio</span>
<audio id="img-card" controls></audio><br>
</div>
<div class="waves-effect waves-light btn" id="upload-button" >
<span>Upload and Detect Audio</span>
</div>
<div class="waves-effect waves-light btn" id="go-back">
<span>Go Back</span>
</div>
</div>
<div class="col s12 dt-container">
<div class="card dt-card" id="stat-table">
</div>
<div class="waves-effect waves-light btn" id="go-start">
<span>Go Back</span>
</div>
</div>
<div id="modal1" class="modal">
<div class="modal-content">
<div style="width: 100%; overflow: hidden;">
<div style="width: 100px; float: left;">
<div class="preloader-wrapper big active">
<div class="spinner-layer spinner-blue-only">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
</div>
<div class="loading-text">
<h5>Loading...</h5></div>
</div>
</div>
</div>
</body>
</html>
js
$(document).ready(function() {
var dropContainer = document.getElementById('drop-container');
dropContainer.ondragover = dropContainer.ondragend = function() {
return false;
};
dropContainer.ondrop = function(e) {
e.preventDefault();
loadAudio(e.dataTransfer.files[0])
};
$("#browse-button").change(function() {
loadAudio($("#browse-button").prop("files")[0]);
});
$('.modal').modal({
dismissible: false,
ready: function(modal, trigger) {
$.ajax({
type: "POST",
url:'VC_api/',
data: {
'image64': $('#img-card').attr('src')
},
dataType: 'text',
success: function(data) {
modal.modal('close');
loadStats(data);
},
error: function(data) {
modal.modal('close');
loadStats(data);
},
}).always(function() {
modal.modal('close');
});
}
});
$('#go-back, #go-start').click(function() {
$('#img-card').removeAttr("src");
$('#stat-table').html('');
switchCard(0);
});
$('#upload-button').click(function() {
$('.modal').modal('open');
});
});
switchCard = function(cardNo) {
var containers = [".dd-container", ".uf-container", ".dt-container"];
var visibleContainer = containers[cardNo];
for (var i = 0; i < containers.length; i++) {
var oz = (containers[i] === visibleContainer) ? '1' : '0';
$(containers[i]).animate({
opacity: oz
}, {
duration: 200,
queue: false,
}).css("z-index", oz);
}
};
loadAudio = function(file) {
var reader = new FileReader();
reader.onload = function(event) {
$('#img-card').attr('src', event.target.result);
};
reader.readAsDataURL(file);
switchCard(1);
};
loadStats = function(jsonData) {
switchCard(2);
var data = JSON.parse(jsonData);
if (data["success"] == true) {
var markup = `
<span>source audio</span><br>
<audio id="result" src="/static/audio/test.wav" controls></audio><br>
<span>target audio</span><br>
<audio id="result2" src="/static/audio/y_test.wav" controls></audio><br>
`;
$("#stat-table").append(markup);
}
};