vue實(shí)現(xiàn)錄音功能js-audio-recorder帶波浪圖效果的示例
前言:
因?yàn)闃I(yè)務(wù)需要,現(xiàn)在將整理的錄音功能資料記錄下,使用插件js-audio-recorder
實(shí)現(xiàn)效果:可得到三種錄音數(shù)據(jù),pcm,wav,mp3 等


官方api入口:點(diǎn)我(網(wǎng)不好的童鞋可以看最下面的api截圖)
官方案例入口:點(diǎn)我
官方源碼git入口:點(diǎn)我
實(shí)現(xiàn)步驟:
一:安裝插件 js-audio-recorder
cnpm i?js-audio-recorder?--s
二:安裝將格式轉(zhuǎn)換為mp3的插件 lamejs
cnpm install lamejs --s
三:附上實(shí)現(xiàn)源碼:
提示:慎用自身的這個(gè)監(jiān)聽事件,可以拿到數(shù)據(jù),但是每秒拿到的數(shù)據(jù)非常多

<template>
<div class="home" style="margin:1vw;">
<Button type="success" @click="getPermission()" style="margin:1vw;">獲取麥克風(fēng)權(quán)限</Button>
<br/>
<Button type="info" @click="startRecorder()" style="margin:1vw;">開始錄音</Button>
<Button type="info" @click="resumeRecorder()" style="margin:1vw;">繼續(xù)錄音</Button>
<Button type="info" @click="pauseRecorder()" style="margin:1vw;">暫停錄音</Button>
<Button type="info" @click="stopRecorder()" style="margin:1vw;">結(jié)束錄音</Button>
<br/>
<Button type="success" @click="playRecorder()" style="margin:1vw;">錄音播放</Button>
<Button type="success" @click="pausePlayRecorder()" style="margin:1vw;">暫停錄音播放</Button>
<Button type="success" @click="resumePlayRecorder()" style="margin:1vw;">恢復(fù)錄音播放</Button>
<Button type="success" @click="stopPlayRecorder()" style="margin:1vw;">停止錄音播放</Button>
<br/>
<Button type="info" @click="getRecorder()" style="margin:1vw;">獲取錄音信息</Button>
<Button type="info" @click="downPCM()" style="margin:1vw;">下載PCM</Button>
<Button type="info" @click="downWAV()" style="margin:1vw;">下載WAV</Button>
<Button type="info" @click="getMp3Data()" style="margin:1vw;">下載MP3</Button>
<br/>
<Button type="error" @click="destroyRecorder()" style="margin:1vw;">銷毀錄音</Button>
<br/>
<div style="width:100%;height:200px;border:1px solid red;">
<canvas id="canvas"></canvas>
<span style="padding: 0 10%;"></span>
<canvas id="playChart"></canvas>
</div>
</div>
</template>
<script>
import Recorder from 'js-audio-recorder'
const lamejs = require('lamejs')
const recorder = new Recorder({
sampleBits: 16, // 采樣位數(shù),支持 8 或 16,默認(rèn)是16
sampleRate: 48000, // 采樣率,支持 11025、16000、22050、24000、44100、48000,根據(jù)瀏覽器默認(rèn)值,我的chrome是48000
numChannels: 1, // 聲道,支持 1 或 2, 默認(rèn)是1
// compiling: false,(0.x版本中生效,1.x增加中) // 是否邊錄邊轉(zhuǎn)換,默認(rèn)是false
})
// 綁定事件-打印的是當(dāng)前錄音數(shù)據(jù)
recorder.onprogress = function(params) {
// console.log('--------------START---------------')
// console.log('錄音時(shí)長(秒)', params.duration);
// console.log('錄音大小(字節(jié))', params.fileSize);
// console.log('錄音音量百分比(%)', params.vol);
// console.log('當(dāng)前錄音的總數(shù)據(jù)([DataView, DataView...])', params.data);
// console.log('--------------END---------------')
}
export default {
name: 'home',
data () {
return {
//波浪圖-錄音
drawRecordId:null,
oCanvas : null,
ctx : null,
//波浪圖-播放
drawPlayId:null,
pCanvas : null,
pCtx : null,
}
},
mounted(){
this.startCanvas();
},
methods: {
/**
* 波浪圖配置
* */
startCanvas(){
//錄音波浪
this.oCanvas = document.getElementById('canvas');
this.ctx = this.oCanvas.getContext("2d");
//播放波浪
this.pCanvas = document.getElementById('playChart');
this.pCtx = this.pCanvas.getContext("2d");
},
/**
* 錄音的具體操作功能
* */
// 開始錄音
startRecorder () {
recorder.start().then(() => {
this.drawRecord();//開始繪制圖片
}, (error) => {
// 出錯(cuò)了
console.log(`${error.name} : ${error.message}`);
});
},
// 繼續(xù)錄音
resumeRecorder () {
recorder.resume()
},
// 暫停錄音
pauseRecorder () {
recorder.pause();
this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
this.drawRecordId = null;
},
// 結(jié)束錄音
stopRecorder () {
recorder.stop()
this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
this.drawRecordId = null;
},
// 錄音播放
playRecorder () {
recorder.play();
this.drawPlay();//繪制波浪圖
},
// 暫停錄音播放
pausePlayRecorder () {
recorder.pausePlay()
},
// 恢復(fù)錄音播放
resumePlayRecorder () {
recorder.resumePlay();
this.drawPlay();//繪制波浪圖
},
// 停止錄音播放
stopPlayRecorder () {
recorder.stopPlay();
},
// 銷毀錄音
destroyRecorder () {
recorder.destroy().then(function() {
recorder = null;
this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
this.drawRecordId = null;
});
},
/**
* 獲取錄音文件
* */
getRecorder(){
let toltime = recorder.duration;//錄音總時(shí)長
let fileSize = recorder.fileSize;//錄音總大小
//錄音結(jié)束,獲取取錄音數(shù)據(jù)
let PCMBlob = recorder.getPCMBlob();//獲取 PCM 數(shù)據(jù)
let wav = recorder.getWAVBlob();//獲取 WAV 數(shù)據(jù)
let channel = recorder.getChannelData();//獲取左聲道和右聲道音頻數(shù)據(jù)
},
/**
* 下載錄音文件
* */
//下載pcm
downPCM(){
//這里傳參進(jìn)去的時(shí)文件名
recorder.downloadPCM('新文件');
},
//下載wav
downWAV(){
//這里傳參進(jìn)去的時(shí)文件名
recorder.downloadWAV('新文件');
},
/**
* 獲取麥克風(fēng)權(quán)限
* */
getPermission(){
Recorder.getPermission().then(() => {
this.$Message.success('獲取權(quán)限成功')
}, (error) => {
console.log(`${error.name} : ${error.message}`);
});
},
/**
* 文件格式轉(zhuǎn)換 wav-map3
* */
getMp3Data(){
const mp3Blob = this.convertToMp3(recorder.getWAV());
recorder.download(mp3Blob, 'recorder', 'mp3');
},
convertToMp3(wavDataView) {
// 獲取wav頭信息
const wav = lamejs.WavHeader.readHeader(wavDataView); // 此處其實(shí)可以不用去讀wav頭信息,畢竟有對應(yīng)的config配置
const { channels, sampleRate } = wav;
const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);
// 獲取左右通道數(shù)據(jù)
const result = recorder.getChannelData()
const buffer = [];
const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2);
const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2);
const remaining = leftData.length + (rightData ? rightData.length : 0);
const maxSamples = 1152;
for (let i = 0; i < remaining; i += maxSamples) {
const left = leftData.subarray(i, i + maxSamples);
let right = null;
let mp3buf = null;
if (channels === 2) {
right = rightData.subarray(i, i + maxSamples);
mp3buf = mp3enc.encodeBuffer(left, right);
} else {
mp3buf = mp3enc.encodeBuffer(left);
}
if (mp3buf.length > 0) {
buffer.push(mp3buf);
}
}
const enc = mp3enc.flush();
if (enc.length > 0) {
buffer.push(enc);
}
return new Blob(buffer, { type: 'audio/mp3' });
},
/**
* 繪制波浪圖-錄音
* */
drawRecord () {
// 用requestAnimationFrame穩(wěn)定60fps繪制
this.drawRecordId = requestAnimationFrame(this.drawRecord);
// 實(shí)時(shí)獲取音頻大小數(shù)據(jù)
let dataArray = recorder.getRecordAnalyseData(),
bufferLength = dataArray.length;
// 填充背景色
this.ctx.fillStyle = 'rgb(200, 200, 200)';
this.ctx.fillRect(0, 0, this.oCanvas.width, this.oCanvas.height);
// 設(shè)定波形繪制顏色
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = 'rgb(0, 0, 0)';
this.ctx.beginPath();
var sliceWidth = this.oCanvas.width * 1.0 / bufferLength, // 一個(gè)點(diǎn)占多少位置,共有bufferLength個(gè)點(diǎn)要繪制
x = 0; // 繪制點(diǎn)的x軸位置
for (var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 128.0;
var y = v * this.oCanvas.height / 2;
if (i === 0) {
// 第一個(gè)點(diǎn)
this.ctx.moveTo(x, y);
} else {
// 剩余的點(diǎn)
this.ctx.lineTo(x, y);
}
// 依次平移,繪制所有點(diǎn)
x += sliceWidth;
}
this.ctx.lineTo(this.oCanvas.width, this.oCanvas.height / 2);
this.ctx.stroke();
},
/**
* 繪制波浪圖-播放
* */
drawPlay () {
// 用requestAnimationFrame穩(wěn)定60fps繪制
this.drawPlayId = requestAnimationFrame(this.drawPlay);
// 實(shí)時(shí)獲取音頻大小數(shù)據(jù)
let dataArray = recorder.getPlayAnalyseData(),
bufferLength = dataArray.length;
// 填充背景色
this.pCtx.fillStyle = 'rgb(200, 200, 200)';
this.pCtx.fillRect(0, 0, this.pCanvas.width, this.pCanvas.height);
// 設(shè)定波形繪制顏色
this.pCtx.lineWidth = 2;
this.pCtx.strokeStyle = 'rgb(0, 0, 0)';
this.pCtx.beginPath();
var sliceWidth = this.pCanvas.width * 1.0 / bufferLength, // 一個(gè)點(diǎn)占多少位置,共有bufferLength個(gè)點(diǎn)要繪制
x = 0; // 繪制點(diǎn)的x軸位置
for (var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 128.0;
var y = v * this.pCanvas.height / 2;
if (i === 0) {
// 第一個(gè)點(diǎn)
this.pCtx.moveTo(x, y);
} else {
// 剩余的點(diǎn)
this.pCtx.lineTo(x, y);
}
// 依次平移,繪制所有點(diǎn)
x += sliceWidth;
}
this.pCtx.lineTo(this.pCanvas.width, this.pCanvas.height / 2);
this.pCtx.stroke();
}
},
}
</script>
<style lang='less' scoped>
</style>到這里,代碼就結(jié)束了,上面每個(gè)方法都有很詳細(xì)的注釋,就不累贅了
整理api:(有代理的可以看官網(wǎng),這里是摘取官網(wǎng)的api)
1,使用
安裝
npm 方式
推薦使用npm安裝的方式:
安裝:
npm i js-audio-recorder
調(diào)用:
import Recorder from 'js-audio-recorder'; let recorder = new Recorder();
script 標(biāo)簽方式
<script type="text/javascript" src="./dist/recorder.js"></script> let recorder = new Recorder();
2,屬性
實(shí)例初始化
可以配置輸出數(shù)據(jù)參數(shù),
let recorder = new Recorder({
sampleBits: 16, // 采樣位數(shù),支持 8 或 16,默認(rèn)是16
sampleRate: 16000, // 采樣率,支持 11025、16000、22050、24000、44100、48000,根據(jù)瀏覽器默認(rèn)值,我的chrome是48000
numChannels: 1, // 聲道,支持 1 或 2, 默認(rèn)是1
// compiling: false,(0.x版本中生效,1.x增加中) // 是否邊錄邊轉(zhuǎn)換,默認(rèn)是false
});返回: recorder實(shí)例。
sampleBits
采樣位數(shù)。
sampleRate
采樣率。
numChannels
聲道數(shù)。
compiling
(0.x版本中生效,最新目前不支持)
是否邊錄音邊轉(zhuǎn)換。
獲取數(shù)據(jù)方法:
回調(diào)方式
recorder.onprogress = function(params) {
console.log(params.data); // 當(dāng)前獲取到到音頻數(shù)據(jù)
}data,DataView型數(shù)組,格式如 [DataView, DataView, DataView ...] 。
主動(dòng)獲取
getWholeData(); // [DataView, DataView, DataView ...] getNextData(); // [DataView, DataView, DataView ...]
getWholeData() 的值和onprogress回調(diào)中的data數(shù)據(jù)一致。
getNextData() 獲取的是前一次 getNextData() 之后的值,他只是data數(shù)據(jù)的一小部分。
實(shí)例屬性
duration
獲取錄音的總時(shí)長。
console.log(recorder.duration);
fileSize
錄音文件大?。▎挝唬鹤止?jié))。
console.log(recorder.fileSize);
3,操作
start()
開始錄音。
返回: Promise。
recorder.start().then(() => {
// 開始錄音
}, (error) => {
// 出錯(cuò)了
console.log(`${error.name} : ${error.message}`);
});pause()
錄音暫停。
返回: void
recorder.pause();
resume()
繼續(xù)錄音。
返回: void。
recorder.resume()
stop()
結(jié)束錄音。
返回: void。
recorder.stop();
play()
錄音播放。
返回: void。
recorder.play();
getPlayTime()
獲取音頻已經(jīng)播的時(shí)長。
返回: number。
recorder.getPlayTime();
pausePlay()
暫停錄音播放。
返回: void。
recorder.pausePlay();
resumePlay()
恢復(fù)錄音播發(fā)。
返回: void。
recorder.resumePlay();
stopPlay()
停止播放。
返回: void。
recorder.stopPlay();
destroy()
銷毀實(shí)例。
返回: Promise。
// 銷毀錄音實(shí)例,置為null釋放資源,fn為回調(diào)函數(shù),
recorder.destroy().then(function() {
recorder = null;
});音頻數(shù)據(jù)
錄音結(jié)束,獲取取錄音數(shù)據(jù)
getPCMBlob()
獲取 PCM 數(shù)據(jù),在錄音結(jié)束后使用。
返回: Blob
注:使用該方法會(huì)默認(rèn)調(diào)用 stop() 方法。
recorder.getPCMBlob();
getWAVBlob()
獲取 WAV 數(shù)據(jù),在錄音結(jié)束后使用
返回: Blob
注:使用該方法會(huì)默認(rèn)調(diào)用 stop() 方法。
recorder.getWAVBlob();
getChannelData()
獲取左聲道和右聲道音頻數(shù)據(jù)。
recorder.getChannelData();
錄音結(jié)束,下載錄音文件
downloadPCM([ filename ])
下載 PCM 格式
- fileName String 重命名文件
- 返回: Blob
注:使用該方法會(huì)默認(rèn)調(diào)用 stop() 方法。
recorder.downloadPCM(fileName ?);
downloadWAV([ filename ])
下載 WAV 格式
- fileName String 重命名文件
- 返回: Blob
注:使用該方法會(huì)默認(rèn)調(diào)用 stop() 方法。
錄音中,獲取錄音數(shù)據(jù)
(0.x版本中生效,最新目前不支持)
該方式為邊錄邊轉(zhuǎn)換,建議在 compiling 為 true 時(shí)使用。
getWholeData()
獲取已經(jīng)錄音的所有數(shù)據(jù)。若沒有開啟邊錄邊轉(zhuǎn)(compiling為false),則返回是空數(shù)組。
返回: Array, 數(shù)組中是DataView數(shù)據(jù)
定時(shí)獲取所有數(shù)據(jù):
setInterval(() => {
recorder.getWholeData();
}, 1000)getNextData()
獲取前一次 getNextData() 之后的數(shù)據(jù)。若沒有開啟邊錄邊轉(zhuǎn)(compiling為false),則返回是空數(shù)組。
- 返回: Array, 數(shù)組中是DataView數(shù)據(jù)
定時(shí)獲取新增數(shù)據(jù):
setInterval(() => {
recorder.getNextData();
}, 1000)
// 實(shí)時(shí)錄音,則可將該數(shù)據(jù)返回給服務(wù)端。錄音波形顯示
getRecordAnalyseData()
返回的是一個(gè)1024長的,0-255大小的Uint8Array類型。用戶可以根據(jù)這些數(shù)據(jù)自定義錄音波形。此接口獲取的是錄音時(shí)的。
let dataArray = recorder.getRecordAnalyseData();
getPlayAnalyseData()
返回?cái)?shù)據(jù)同 getRecordAnalyseData(),該方法獲取的是播放時(shí)的。
let dataArray = recorder.getPlayAnalyseData();
播放外部
Player.play(blob)
播放外部音頻,格式由瀏覽器的audio支持的類型決定。
Player.play(/* 放入arraybuffer數(shù)據(jù) */);
其他
錄音權(quán)限
未給予錄音權(quán)限的頁面在開始錄音時(shí)需要再次點(diǎn)擊允許錄音,才能真正地錄音,存在丟失開始這一段錄音的情況,增加方法以便用戶提前獲取麥克風(fēng)權(quán)限。
getPermission()
獲取麥克風(fēng)權(quán)限。
返回:promise。
Recorder.getPermission().then(() => {
console.log('給權(quán)限了');
}, (error) => {
console.log(`${error.name} : ${error.message}`);
});此處then回調(diào)與start的一致。
4,Event
js-audio-recorder 支持的事件回調(diào)。
onprocess(duration)
用于獲取錄音時(shí)長。
不推薦使用,用onprogress代替。
recorder.onprocess = function(duration) {
console.log(duration);
}onprogress(duration)
目前支持獲取以下數(shù)據(jù):
- 錄音時(shí)長(duration)。
- 錄音大?。╢ileSize)。
- 錄音音量百分比(vol)。
- 所有的錄音數(shù)據(jù)(data)。
recorder.onprogress = function(params) {
console.log('錄音時(shí)長(秒)', params.duration);
console.log('錄音大小(字節(jié))', params.fileSize);
console.log('錄音音量百分比(%)', params.vol);
// console.log('當(dāng)前錄音的總數(shù)據(jù)([DataView, DataView...])', params.data);
}onplay
錄音播放開始回調(diào)。
recorder.onplay = () => {
console.log('onplay')
}onpauseplay
錄音播放暫?;卣{(diào)。
recorder.onpauseplay = () => {
console.log('onpauseplay')
}onresumeplay
錄音播放恢復(fù)回調(diào)。
recorder.onresumeplay = () => {
console.log('onresumeplay')
}onstopplay
錄音播放停止回調(diào)。
recorder.onstopplay = () => {
console.log('onstopplay')
}onplayend
錄音播放完成回調(diào)。
recorder.onplayend = () => {
console.log('onplayend')
}5,應(yīng)用
語音識(shí)別
recorder上可以測試,注意選擇16000采樣率,16采樣位數(shù),單聲道錄音。
6,PlayerPlayer 播放類
import Player from './player/player';
用于協(xié)助播放錄音文件,包括,開始、暫停、恢復(fù)、停止等功能。所支持的格式由瀏覽器的audio支持的類型決定??蓡为?dú)使用。
Player.play([arraybuffer])
播放外部的音頻。所支持的格式由瀏覽器的audio支持的類型決定。
實(shí)際是調(diào)用了decodeAudioData實(shí)現(xiàn)音頻播放。
Recorder.play(/* 放入arraybuffer數(shù)據(jù) */);
Player.pausePlay()
暫停播放。
Player.resumePlay()
恢復(fù)播放。
Player.stopPlay()
停止播放。
Player.addPlayEnd(fn)
增加播放完成回調(diào)函數(shù)。
Player.getPlayTime()
獲取播放時(shí)間。
Player.getAnalyseData()
獲取回放錄音的波形數(shù)據(jù)。
7,其他音頻格式
MP3
將pcm(wav)音頻文件轉(zhuǎn)化為mp3格式。
注:使用16采樣位數(shù)。
利用lamejs進(jìn)行轉(zhuǎn)換,使用情況見demo,例子:
function convertToMp3(wavDataView) {
// 獲取wav頭信息
const wav = lamejs.WavHeader.readHeader(wavDataView); // 此處其實(shí)可以不用去讀wav頭信息,畢竟有對應(yīng)的config配置
const { channels, sampleRate } = wav;
const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);
// 獲取左右通道數(shù)據(jù)
const result = recorder.getChannelData()
const buffer = [];
const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2);
const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2);
const remaining = leftData.length + (rightData ? rightData.length : 0);
const maxSamples = 1152;
for (let i = 0; i < remaining; i += maxSamples) {
const left = leftData.subarray(i, i + maxSamples);
let right = null;
let mp3buf = null;
if (channels === 2) {
right = rightData.subarray(i, i + maxSamples);
mp3buf = mp3enc.encodeBuffer(left, right);
} else {
mp3buf = mp3enc.encodeBuffer(left);
}
if (mp3buf.length > 0) {
buffer.push(mp3buf);
}
}
const enc = mp3enc.flush();
if (enc.length > 0) {
buffer.push(enc);
}
return new Blob(buffer, { type: 'audio/mp3' });
}安裝lamejs
npm install lamejs
到此這篇關(guān)于vue實(shí)現(xiàn)錄音功能js-audio-recorder帶波浪圖效果的示例的文章就介紹到這了,更多相關(guān)vue js-audio-recorder錄音內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- vue實(shí)現(xiàn)PC端錄音功能的實(shí)例代碼
- vue使用recorder.js實(shí)現(xiàn)錄音功能
- Vue實(shí)現(xiàn)懸浮框自由移動(dòng)+錄音功能的示例代碼
- vue使用recorder-core.js實(shí)現(xiàn)錄音功能
- vue使用js-audio-recorder實(shí)現(xiàn)錄音功能
- vue實(shí)現(xiàn)錄音并轉(zhuǎn)文字功能包括PC端web手機(jī)端web(實(shí)現(xiàn)過程)
- Vue如何使用js-audio-recorder插件實(shí)現(xiàn)錄音功能并將文件轉(zhuǎn)成wav上傳
相關(guān)文章
有關(guān)vue 組件切換,動(dòng)態(tài)組件,組件緩存
這篇文章主要介紹了有關(guān)vue 組件切換,動(dòng)態(tài)組件,組件緩存,在組件化開發(fā)模式下,我們會(huì)把整個(gè)項(xiàng)目拆分成很多組件,然后按照合理的方式組織起來,達(dá)到預(yù)期效果,下面來看看文章的詳細(xì)內(nèi)容2021-11-11
vue搭建本地JSON靜態(tài)數(shù)據(jù)服務(wù)器全過程
這篇文章主要介紹了vue搭建本地JSON靜態(tài)數(shù)據(jù)服務(wù)器全過程,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
iView-admin 動(dòng)態(tài)路由問題的解決方法
這篇文章主要介紹了iView-admin 動(dòng)態(tài)路由問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
Vue3+Vite中不支持require的方式引入本地圖片的解決方案
這篇文章主要介紹了Vue3+Vite中不支持require的方式引入本地圖片的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
vue實(shí)現(xiàn)websocket客服聊天功能
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)websocket客服聊天功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10

