MockingBird/web/templates/index.html

374 lines
13 KiB
HTML
Raw Normal View History

<!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>