This commit is contained in:
yumoqing 2024-01-30 18:11:44 +08:00
parent 76abe0935f
commit a47d155bf9
7 changed files with 142 additions and 60 deletions

View File

@ -1,4 +1,16 @@
var formatMs=function(ms,all){
var ss=ms%1000;ms=(ms-ss)/1000;
var s=ms%60;ms=(ms-s)/60;
var m=ms%60;ms=(ms-m)/60;
var h=ms;
var t=(h?h+":":"")
+(all||h+m?("0"+m).substr(-2)+":":"")
+(all||h+m+s?("0"+s).substr(-2)+"″":"")
+("00"+ss).substr(-3);
return t;
};
class AudioPlayer extends JsWidget { class AudioPlayer extends JsWidget {
/* /*
{ {
@ -35,6 +47,7 @@ class AudioPlayer extends JsWidget {
} }
this.player.source.src = url;
//webkitURL is deprecated but nevertheless //webkitURL is deprecated but nevertheless
var URL = window.URL || window.webkitURL; var URL = window.URL || window.webkitURL;
var AudioContext = window.AudioContext || window.webkitAudioContext; var AudioContext = window.AudioContext || window.webkitAudioContext;
@ -44,66 +57,135 @@ class AudioRecorder extends HBox {
super(opts); super(opts);
this.start_icon = opts.start_icon || bricks_resource('imgs/start_recording.png'); this.start_icon = opts.start_icon || bricks_resource('imgs/start_recording.png');
this.stop_icon = opts.stop_icon || bricks_resource('imgs/stop_recording.png'); this.stop_icon = opts.stop_icon || bricks_resource('imgs/stop_recording.png');
console.log('this.start_icon=', this.start_icon, this.stop_icon); this.rec = null;
this.gumStream = null; //stream from getUserMedia() this.wave = null;
this.recorder = null; this.recBlob = null;
// shim for AudioContext when it's not avb. this.recOpen();
this.img = new Image({height:'100%', url:this.start_icon}); }
this.player = new AudioPlayer({}); on_process(buffers,powerLevel,
this.player.disabled(true); bufferDuration,bufferSampleRate,
this.add_widget(this.img); newBufferIdx,asyncEnd){
this.add_widget(this.player); //录音实时回调大约1秒调用12次本回调
this.img.bind('click', this.btn_clicked.bind(this)); document.querySelector(".recpowerx").style.width=powerLevel+"%";
this.audioContext = null; document.querySelector(".recpowert").innerText=formatMs(bufferDuration,1)+" / "+powerLevel;
this.stream = null;
this.chunks = []; //可视化图形绘制
wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);
}
recOpen(){
this.rec=null;
this.wave=null;
this.recBlob=null;
var newRec=Recorder({
type:"wav",sampleRate:16000,bitRate:16
,onProcess:this.on_process.bind(this)
}
});
newRec.open(function(){//打开麦克风授权获得相关资源
this.rec=newRec;
//此处创建这些音频可视化图形绘制浏览器支持妥妥的
this.wave=Recorder.FrequencyHistogramView({elem:".recwave"});
}.bind(this),function(msg,isUserNotAllow){//用户拒绝未授权或不支持
console.log('open recorder failed');
});
}
recClose(){
if(this.rec){
this.rec.close();
}else{
console.log('close recorder error');
};
} }
start_recording(){ start_recording(){
console.log("recordButton clicked", this); if(this.rec&&Recorder.IsOpen()){
var constraints = { audio: true, video:false } this.recBlob=null;
navigator.mediaDevices.getUserMedia(constraints) this.rec.start();
.then(function(stream) { }else{
console.log("getUserMedia() success, stream created, initializing Recorder.js ...", this); console.log("ajKR::未打开录音");
this.img.set_url(this.stop_icon); };
this.audioContext = new AudioContext(); }
this.gumStream = stream; pause_recording(){
/* use the stream */ if(this.rec&&Recorder.IsOpen()){
try { this.rec.pause();
var input = this.audioContext.createMediaStreamSource(stream); }else{
} catch(e){ console.log("gCAR::未打开录音");
console.log('e====', e); };
} }
this.recorder = new Recorder(input,{numChannels:1}) resume_recording(){
this.recorder.record() if(this.rec&&Recorder.IsOpen()){
console.log("Recording started"); this.rec.resume();
}.bind(this)).catch(function(err) { }else{
//enable the record button if getUserMedia() fails console.log("Ob6S::未打开录音");
this.img.set_url(this.start_icon); };
}.bind(this));
} }
stop_recording(){ stop_recording(){
this.img.set_url(this.start_icon); if(!(this.rec&&Recorder.IsOpen())){
this.recorder.stop(); console.log("5JuL::未打开录音");
this.gumStream.getAudioTracks()[0].stop(); return;
this.recorder.exportWAV(this.createWAV.bind(this)); };
this.rec.stop(function(blob,duration){
console.log(blob,(window.URL||webkitURL).createObjectURL(blob),Html_xT(Html_$T("gOix::时长:{1}ms",0,duration)));
this.recBlob=blob;
console.log("0LHf::已录制mp3{1}ms {2}字节,可以点击播放、上传、本地下载了",0,formatMs(duration),blob.size);
}.bind(this),function(msg){
console.log("kGZO::录音失败:");
});
} }
createWAV(blob){ play_record(){
console.log('reord stoped .....', this); if(!this.recBlob){
var url = URL.createObjectURL(blob); console.log("tIke::请先录音,然后停止后再播放");
this.player.set_source(url); return;
this.player.disabled(false); };
console.log('---------done------------', url); var cls=("a"+Math.random()).replace(".","");
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);
} }
btn_clicked(){ upload(){
if (!this.isRecording){ var blob=this.recBlob;
this.img.src = this.stop_icon; if(!blob){
this.isRecording = true; console.log("DUTn::请先录音,然后停止后再上传");
this.start_recording(); return;
} else { };
this.img.src = this.start_icon; var form=new FormData();
this.isRecording = false; form.append("audiofile",blob,"recorder.wav");
this.stop_recording(); const data = new URLSearchParams();
for (const pair of form)) {
data.append(pair[0], pair[1]);
} }
hj = HttpJson()
ret = await hj.post(this.upload_url,{
params:data
});
console.log('ret=', ret);
this.dispatch('updated', ret)
}
download(){
if(!this.recBlob){
console.log('recorder not opened');
return;
};
var fileName="recorder-"+Date.now()+".wav";
var downA=document.createElement("A");
downA.innerHTML="下载")+fileName;
downA.href=(window.URL||webkitURL).createObjectURL(this.recBlob);
downA.download=fileName;
# document.querySelector("."+cls).appendChild(downA);
if(/mobile/i.test(navigator.userAgent)){
;
}
downA.click();
//不用了时需要revokeObjectURL否则霸占内存
(window.URL||webkitURL).revokeObjectURL(downA.href);
} }
} }

View File

@ -7,6 +7,7 @@
<link rel="stylesheet" href="/bricks/css/bricks.css"> <link rel="stylesheet" href="/bricks/css/bricks.css">
</head> </head>
<body> <body>
<script src="/3parties/recorder.wav.min.js"></script>
<script src="/bricks/bricks.js"></script> <script src="/bricks/bricks.js"></script>
<script> <script>
const opts = { const opts = {

BIN
bricks/imgs/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
bricks/imgs/stop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
bricks/imgs/upload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -40,15 +40,17 @@ class HttpText {
add_own_params(params){ add_own_params(params){
if (! params) if (! params)
params = {}; params = {};
var session = bricks_app.get_session();
if (params instanceof FormData){ if (params instanceof FormData){
for ( const [key, value] of Object.entries(this.params)){ for ( const [key, value] of Object.entries(this.params)){
params.append(key, value); params.append(key, value);
} }
if(session){
params.append('session', session);
}
return params;
} }
else { params = Object.assign(this.params, params);
params = Object.assign(this.params, params);
}
var session = bricks_app.get_session();
if (session){ if (session){
extend(params,{session:session}); extend(params,{session:session});
} }
@ -60,7 +62,6 @@ class HttpText {
} }
return Object.assign(this.headers, headers); return Object.assign(this.headers, headers);
} }
async httpcall(url, {method='GET', headers=null, params=null}={}){ async httpcall(url, {method='GET', headers=null, params=null}={}){
url = this.url_parse(url); url = this.url_parse(url);
var data = this.add_own_params(params); var data = this.add_own_params(params);
@ -93,8 +94,6 @@ class HttpText {
if (fetchResult.status == 401 && bricks_app.login_url){ if (fetchResult.status == 401 && bricks_app.login_url){
return await this.withLoginInfo(url, _params); return await this.withLoginInfo(url, _params);
} }
console.log('method=', method, 'url=', url, 'params=', params);
console.log('jsoncall error:');
const resp_error = { const resp_error = {
"type":"Error", "type":"Error",
"message":result.message || 'Something went wrong', "message":result.message || 'Something went wrong',