mirror of
https://github.com/babysor/MockingBird.git
synced 2024-03-22 13:11:31 +08:00
ddd478c0ad
* Init App * init server.py (#93) * init server.py * Update requirements.txt Add requirement Co-authored-by: auau <auau@test.com> Co-authored-by: babysor00 <babysor00@gmail.com> * Run web.py! Run web.py! Co-authored-by: balala <Ozgay@users.noreply.github.com> Co-authored-by: auau <auau@test.com>
374 lines
13 KiB
HTML
374 lines
13 KiB
HTML
<!DOCTYPE HTML>
|
||
<html>
|
||
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||
<link rel="shortcut icon" type="image/png"
|
||
href="https://cdn.jsdelivr.net/gh/xiangyuecn/Recorder@latest/assets/icon.png">
|
||
|
||
<title>MockingBird Web Server</title>
|
||
|
||
<script src="{{ url_for('static',filename='js/recorder-core.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='js/mp3.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='js/wav.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='js/mp3-engine.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='js/frequency.histogram.view.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='js/lib.fft.js') }}"></script>
|
||
|
||
<script src="{{ url_for('static',filename='js/jquery.js') }}"></script>
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<div class="main">
|
||
|
||
<div class="mainBox">
|
||
<div class="pd btns">
|
||
<!-- <div>
|
||
<button onclick="recOpen()" style="margin-right:10px">打开录音,请求权限</button>
|
||
<button onclick="recClose()" style="margin-right:0">关闭录音,释放资源</button>
|
||
</div> -->
|
||
<button onclick="recStart()" style="margin-left:100px">录制</button>
|
||
<button onclick="recStop()" style="margin-left:100px">停止</button>
|
||
<button onclick="recPlay()" style="margin-left:100px">播放</button>
|
||
</div>
|
||
|
||
<!-- 波形绘制区域 -->
|
||
<div class="pd recpower">
|
||
<div style="height:40px;width:100%;background:#fff;position:relative;">
|
||
<div class="recpowerx" style="height:40px;background:#ff3295;position:absolute;"></div>
|
||
<div class="recpowert" style="padding-left:50px; line-height:40px; position: relative;"></div>
|
||
</div>
|
||
</div>
|
||
<div class="pd waveBox" style="height:100px;">
|
||
<div style="border:1px solid #ccc;display:inline-block; width: 100%; height: 100px;">
|
||
<div style="height:100px; width: 100%; background-color: #FE76B8; position: relative;left: 0px;top: 0px;z-index: 10;"
|
||
class="recwave"></div>
|
||
<div
|
||
style="background-color: transparent;position: relative;top: -80px;left: 30%;z-index: 20;font-size: 48px;color: #fff;">
|
||
音频预览</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div>请输入文本:</div>
|
||
<input type="text" id="user_input_text"
|
||
style="border:1px solid #ccc; width: 100%; height: 20px; font-size: 18px;" />
|
||
</div>
|
||
<div class="pd btns">
|
||
<button onclick="recUpload()" style="margin-left: 300px; margin-top: 15px;">上传</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 日志输出区域 -->
|
||
<div class="mainBox">
|
||
<div class="reclog"></div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<script>
|
||
var rec, wave, recBlob;
|
||
/**调用open打开录音请求好录音权限**/
|
||
var recOpen = function () {//一般在显示出录音按钮或相关的录音界面时进行此方法调用,后面用户点击开始录音时就能畅通无阻了
|
||
rec = null;
|
||
wave = null;
|
||
recBlob = null;
|
||
var newRec = Recorder({
|
||
type: "wav", bitRate: 16, sampleRate: 16000
|
||
, onProcess: function (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) {
|
||
//录音实时回调,大约1秒调用12次本回调
|
||
document.querySelector(".recpowerx").style.width = powerLevel + "%";
|
||
document.querySelector(".recpowert").innerText = bufferDuration + " / " + powerLevel;
|
||
|
||
//可视化图形绘制
|
||
wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
|
||
}
|
||
});
|
||
|
||
createDelayDialog(); //我们可以选择性的弹一个对话框:为了防止移动端浏览器存在第三种情况:用户忽略,并且(或者国产系统UC系)浏览器没有任何回调,此处demo省略了弹窗的代码
|
||
newRec.open(function () {//打开麦克风授权获得相关资源
|
||
dialogCancel(); //如果开启了弹框,此处需要取消
|
||
|
||
rec = newRec;
|
||
|
||
//此处创建这些音频可视化图形绘制浏览器支持妥妥的
|
||
wave = Recorder.FrequencyHistogramView({ elem: ".recwave" });
|
||
|
||
reclog("已打开录音,可以点击录制开始录音了", 2);
|
||
}, function (msg, isUserNotAllow) {//用户拒绝未授权或不支持
|
||
dialogCancel(); //如果开启了弹框,此处需要取消
|
||
reclog((isUserNotAllow ? "UserNotAllow," : "") + "打开录音失败:" + msg, 1);
|
||
});
|
||
|
||
window.waitDialogClick = function () {
|
||
dialogCancel();
|
||
reclog("打开失败:权限请求被忽略,<span style='color:#f00'>用户主动点击的弹窗</span>", 1);
|
||
};
|
||
};
|
||
|
||
|
||
|
||
/**关闭录音,释放资源**/
|
||
function recClose() {
|
||
if (rec) {
|
||
rec.close();
|
||
reclog("已关闭");
|
||
} else {
|
||
reclog("未打开录音", 1);
|
||
};
|
||
};
|
||
|
||
|
||
|
||
/**开始录音**/
|
||
function recStart() {//打开了录音后才能进行start、stop调用
|
||
if (rec && Recorder.IsOpen()) {
|
||
recBlob = null;
|
||
rec.start();
|
||
reclog("已开始录音...");
|
||
} else {
|
||
reclog("未打开录音,请求录音权限,如已允许录音权限,请再次点击录制", 1);
|
||
recOpen();
|
||
};
|
||
};
|
||
|
||
function recStop() {
|
||
rec.stop(function (blob, duration) {
|
||
rec.close();//释放录音资源
|
||
console.log(blob, (window.URL || webkitURL).createObjectURL(blob), "时长:" + duration + "ms");
|
||
recBlob = blob;
|
||
reclog("已录制wav:" + duration + "ms " + blob.size + "字节,可以点击播放、上传了", 2);
|
||
}, function (msg) {
|
||
reclog("录音失败:" + msg, 1);
|
||
});
|
||
};
|
||
|
||
|
||
/**播放**/
|
||
function recPlay() {
|
||
if (!recBlob) {
|
||
reclog("请先录音,然后停止后再播放", 1);
|
||
return;
|
||
};
|
||
var cls = ("a" + Math.random()).replace(".", "");
|
||
reclog('播放中: <span class="' + cls + '"></span>');
|
||
var audio = document.createElement("audio");
|
||
audio.controls = true;
|
||
document.querySelector("." + cls).appendChild(audio);
|
||
//简单利用URL生成播放地址,注意不用了时需要revokeObjectURL,否则霸占内存
|
||
audio.src = (window.URL || webkitURL).createObjectURL(recBlob);
|
||
audio.play();
|
||
|
||
setTimeout(function () {
|
||
(window.URL || webkitURL).revokeObjectURL(audio.src);
|
||
}, 5000);
|
||
};
|
||
|
||
function playResult(resultBlob) {
|
||
if (!resultBlob) {
|
||
reclog("服务端出错,请重试", 1);
|
||
return;
|
||
};
|
||
var cls = ("a" + Math.random()).replace(".", "");
|
||
reclog('播放中: <span class="' + cls + '"></span>');
|
||
var audio = document.createElement("audio");
|
||
audio.controls = true;
|
||
document.querySelector("." + cls).appendChild(audio);
|
||
//简单利用URL生成播放地址,注意不用了时需要revokeObjectURL,否则霸占内存
|
||
audio.src = (window.URL || webkitURL).createObjectURL(resultBlob);
|
||
audio.play();
|
||
|
||
setTimeout(function () {
|
||
(window.URL || webkitURL).revokeObjectURL(audio.src);
|
||
}, 12000);
|
||
};
|
||
|
||
/**上传**/
|
||
function recUpload() {
|
||
var blob = recBlob;
|
||
if (!blob) {
|
||
reclog("请先录音,然后停止后再上传", 1);
|
||
return;
|
||
};
|
||
|
||
//本例子假设使用原始XMLHttpRequest请求方式,实际使用中自行调整为自己的请求方式
|
||
//录音结束时拿到了blob文件对象,可以用FileReader读取出内容,或者用FormData上传
|
||
var api = "http://127.0.0.1:8080/api/synthesize";
|
||
|
||
reclog("开始上传到" + api + ",请求稍后...");
|
||
|
||
var reader = new FileReader();
|
||
reader.onloadend = function () {
|
||
var csrftoken = "{{ csrf_token() }}";
|
||
var user_input_text = document.getElementById("user_input_text");
|
||
var input_text = user_input_text.value;
|
||
var postData = "";
|
||
postData += "mime=" + encodeURIComponent(blob.type);//告诉后端,这个录音是什么格式的,可能前后端都固定的mp3可以不用写
|
||
postData += "&upfile_b64=" + encodeURIComponent((/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1]) //录音文件内容,后端进行base64解码成二进制
|
||
postData += "&text=" + encodeURIComponent(input_text);
|
||
|
||
fetch(api, {
|
||
method: 'post',
|
||
headers: {
|
||
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||
"X-CSRFToken": csrftoken
|
||
},
|
||
body: postData
|
||
}).then(function (res) {
|
||
if (!res.ok) throw Error(res.statusText);
|
||
return res.blob();
|
||
}).then(function (blob) {
|
||
playResult(blob)
|
||
}).catch(function (err) {
|
||
console.log('Error: ' + err.message);
|
||
})
|
||
};
|
||
reader.readAsDataURL(blob);
|
||
};
|
||
|
||
//recOpen我们可以选择性的弹一个对话框:为了防止移动端浏览器存在第三种情况:用户忽略,并且(或者国产系统UC系)浏览器没有任何回调
|
||
var showDialog = function () {
|
||
if (!/mobile/i.test(navigator.userAgent)) {
|
||
return;//只在移动端开启没有权限请求的检测
|
||
};
|
||
dialogCancel();
|
||
|
||
//显示弹框,应该使用自己的弹框方式
|
||
var div = document.createElement("div");
|
||
document.body.appendChild(div);
|
||
div.innerHTML = (''
|
||
+ '<div class="waitDialog" style="z-index:99999;width:100%;height:100%;top:0;left:0;position:fixed;background:rgba(0,0,0,0.3);">'
|
||
+ '<div style="display:flex;height:100%;align-items:center;">'
|
||
+ '<div style="flex:1;"></div>'
|
||
+ '<div style="width:240px;background:#fff;padding:15px 20px;border-radius: 10px;">'
|
||
+ '<div style="padding-bottom:10px;">录音功能需要麦克风权限,请允许;如果未看到任何请求,请点击忽略~</div>'
|
||
+ '<div style="text-align:center;"><a onclick="waitDialogClick()" style="color:#0B1">忽略</a></div>'
|
||
+ '</div>'
|
||
+ '<div style="flex:1;"></div>'
|
||
+ '</div>'
|
||
+ '</div>');
|
||
};
|
||
var createDelayDialog = function () {
|
||
dialogInt = setTimeout(function () {//定时8秒后打开弹窗,用于监测浏览器没有发起权限请求的情况,在open前放置定时器利于收到了回调能及时取消(不管open是同步还是异步回调的)
|
||
showDialog();
|
||
}, 8000);
|
||
};
|
||
var dialogInt;
|
||
var dialogCancel = function () {
|
||
clearTimeout(dialogInt);
|
||
|
||
//关闭弹框,应该使用自己的弹框方式
|
||
var elems = document.querySelectorAll(".waitDialog");
|
||
for (var i = 0; i < elems.length; i++) {
|
||
elems[i].parentNode.removeChild(elems[i]);
|
||
};
|
||
};
|
||
//recOpen弹框End
|
||
</script>
|
||
|
||
<!--以下这坨可以忽略-->
|
||
<script>
|
||
function reclog(s, color) {
|
||
var now = new Date();
|
||
var t = ("0" + now.getHours()).substr(-2)
|
||
+ ":" + ("0" + now.getMinutes()).substr(-2)
|
||
+ ":" + ("0" + now.getSeconds()).substr(-2);
|
||
var div = document.createElement("div");
|
||
var elem = document.querySelector(".reclog");
|
||
elem.insertBefore(div, elem.firstChild);
|
||
div.innerHTML = '<div style="color:' + (!color ? "" : color == 1 ? "red" : color == 2 ? "#FE76B8" : color) + '">[' + t + ']' + s + '</div>';
|
||
};
|
||
window.onerror = function (message, url, lineNo, columnNo, error) {
|
||
reclog('<span style="color:red">【Uncaught Error】' + message + '<pre>' + "at:" + lineNo + ":" + columnNo + " url:" + url + "\n" + (error && error.stack || "不能获得错误堆栈") + '</pre></span>');
|
||
};
|
||
</script>
|
||
|
||
<script>
|
||
if (/mobile/i.test(navigator.userAgent)) {
|
||
//移动端加载控制台组件
|
||
var elem = document.createElement("script");
|
||
elem.setAttribute("type", "text/javascript");
|
||
|
||
elem.setAttribute("src", "{{ url_for('static',filename='js/eruda.min.js') }}");
|
||
document.body.appendChild(elem);
|
||
elem.onload = function () {
|
||
eruda.init();
|
||
};
|
||
};
|
||
</script>
|
||
|
||
|
||
<style>
|
||
body {
|
||
word-wrap: break-word;
|
||
background: #f5f5f5 center top no-repeat;
|
||
background-size: auto 680px;
|
||
}
|
||
|
||
pre {
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
a {
|
||
text-decoration: none;
|
||
color: #FE76B8;
|
||
}
|
||
|
||
a:hover {
|
||
color: #f00;
|
||
}
|
||
|
||
.main {
|
||
max-width: 700px;
|
||
margin: 0 auto;
|
||
padding-bottom: 80px
|
||
}
|
||
|
||
.mainBox {
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
--border: 1px solid #f60;
|
||
box-shadow: 2px 2px 3px #aaa;
|
||
}
|
||
|
||
|
||
.btns button {
|
||
display: inline-block;
|
||
cursor: pointer;
|
||
border: none;
|
||
border-radius: 3px;
|
||
background: #FE76B8;
|
||
color: #fff;
|
||
padding: 0 15px;
|
||
margin: 3px 20px 3px 0;
|
||
line-height: 36px;
|
||
height: 36px;
|
||
overflow: hidden;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.btns button:active {
|
||
background: #fd54a6
|
||
}
|
||
|
||
.pd {
|
||
padding: 0 0 6px 0;
|
||
}
|
||
|
||
.lb {
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
background: #ff3d9b;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
padding: 2px 8px;
|
||
border-radius: 99px;
|
||
}
|
||
</style>
|
||
|
||
</body>
|
||
|
||
</html> |