开大你的音响,感受HTML5 Audio API带来的视听盛宴

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。

话说HTML5的炫酷真的是让我爱不释手,即使在这个提到IE就伤心不完的年代。但话又说回来,追求卓越Web创造更美世界这样高的追求什么时候又与IE沾过边儿呢?所以当你在看本文并且我们开始讨论HTML5等前沿东西的时候,我们默认是把IE排除在外的。本文的例子可以工作在最新的Chrome及Firefox浏览器下,其他浏览器暂未测试。

// 若下方未出现演示页面请刷新。

 你也可以点此全屏演示  或者前往GitHub进行代码下载然后本地运行。

你还可以 下载示例音乐(如果你手头没有音频文件的话)

文件列表:
bbc_sherlock_openning.mp3
Neptune Illusion Dennis Kuo .mp3
单曲Remix ┃ 爱上这个女声 放进专辑里私藏 夜电播音员.mp3
爱啦啦.mp3

最后,喜欢的朋友可以去GitHub星我(star me)叉我(fork me)。

 

这里将要介绍的HTML5 音频处理接口与Audio标签是不一样的。页面上的Audio标签只是HTML5更语义化的一个表现,而HTML5提供给JavaScript编程用的Audio API则让我们有能力在代码中直接操作原始的音频流数据,对其进行任意加工再造。

展示HTML5 Audio API 最典型直观的一个例子就是跟随音乐节奏变化的频谱图,也称之为可视化效果。本文便是以此为例子展示JavaScript中操作音频数据的。

文中代码仅供参考,实际代码以下载的源码为准。

了解Audio API

一段音频到达扬声器进行播放之前,半路对其进行拦截,于是我们就得到了音频数据了,这个拦截工作是由window.AudioContext来做的,我们所有对音频的操作都基于这个对象。通过AudioContext可以创建不同各类的AudioNode,即音频节点,不同节点作用不同,有的对音频加上滤镜比如提高音色(比如BiquadFilterNode),改变单调,有的音频进行分割,比如将音源中的声道分割出来得到左右声道的声音(ChannelSplitterNode),有的对音频数据进行频谱分析即本文要用到的(AnalyserNode)。

浏览器中的Audio API

统一前缀

JavaScript中处理音频首先需要实例化一个音频上下文类型window.AudioContext。目前Chrome和Firefox对其提供了支持,但需要相应前缀,Chrome中为window.webkitAudioContext,Firefox中为mozAudioContext。所以为了让代码更通用,能够同时工作在两种浏览器中,只需要一句代码将前缀进行统一即可。

window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;

 

这是一种常见的用法,或者操作符'||' 连接起来的表达式中,遇到真值即返回。比如在Chrome中,window.AudioContext为undefined,接着往下走,碰到window.webkitAudioContext不为undefined,表达式在此判断为真值,所以将其返回,于是此时window.AudioContext =window.webkitAudioContext ,所以代码中我们就可以直接使用window.AudioContext 而不用担心具体Chrome还是Firefox了。

var audioContext=new window.AudioContext();

 

考虑浏览器不支持的情况

但这还只是保证了在支持AudioContext的浏览器中能正常工作,如果是在IE中,上面实例化对象的操作会失败,所以有必要加个try catch语句来避免报错。

try {var audioContext = new window.AudioContext();
} catch (e) {Console.log('!Your browser does not support AudioContext');
}

 

这样就安全多啦,妈妈再不担心浏览器报错了。

组织代码

为了更好地进行编码,我们创建一个Visualizer对象,把所有相关属性及方法写到其中。按照惯例,对象的属性直接写在构造器里面,对象的方法写到原型中。对象内部使用的私有方法以短横线开头,不是必要但是种好的命名习惯。

其中设置了一些基本的属性将在后续代码中使用,详细的还请参见源码,这里只简单展示。

复制代码
var Visualizer = function() {this.file = null, //要处理的文件,后面会讲解如何获取文件this.fileName = null, //要处理的文件的名,文件名this.audioContext = null, //进行音频处理的上下文,稍后会进行初始化this.source = null, //保存音频
};
Visualizer.prototype = {_prepareAPI: function() {//统一前缀,方便调用window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;//这里顺便也将requestAnimationFrame也打个补丁,后面用来写动画要用window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;//安全地实例化一个AudioContext并赋值到Visualizer的audioContext属性上,方便后面处理音频使用try {this.audioContext = new AudioContext();} catch (e) {console.log('!妳的浏览器不支持AudioContext:(');console.log(e);}},
}
复制代码

 

加载音频文件

不用说,你肯定得先在代码中获取到音频文件,才能够对其进一步加工。

文件获取的方法

读取文件到JavaScript可以有以下三种方法:

1.新开一个Ajax异步请求来获取文件,如果是本地测试需要关掉浏览器的同源安全策略才能获取成功,不然只能把网页放到服务器上才能正常工作。

具体说来,就是先开一个XMLHttpRequest请求,将文件路径作为请求的URL,并且设置请求返回类型为'ArrayBuffer',这种格式方便我们后续的处理。下面是一个例子。

复制代码
loadSound("sample.mp3"); //调用
// 定义加载音频文件的函数
function loadSound(url) {var request = new XMLHttpRequest(); //建立一个请求request.open('GET', url, true); //配置好请求类型,文件路径等request.responseType = 'arraybuffer'; //配置数据返回类型// 一旦获取完成,对音频进行进一步操作,比如解码request.onload = function() {var arraybuffer = request.response;}request.send();
}
复制代码

 

2.通过文件类型的input来进行文件选择,监听input的onchnage事件,一担文件选中便开始在代码中进行获取处理,此法方便,且不需要工作在服务器上

3.通过拖拽的形式把文件拖放到页面进行获取,比前面一种方法稍微繁杂一点(要监听'dragenter','dragover','drop'等事件)但同样可以很好地在本地环境下工作,无需服务器支持。

更多在JavaScript中获取及处理文件的方法可以见这里

不用说,方法2和3方便本地开发与测试,所以我们两种方法都实现,既支持选择文件,也支持文件拖拽。

通过选择获取

在页面放一个file类型的input。然后在JavaScript中监听它的onchange事件。此事件在input的值发生变化时触发。

对于onchange事件,在Chrome与Firefox中还有一点小的区别,如果你已经选择了一个文件,此时Input就有值了,如果你再次选择同一文件,onchange事件不会触发,但在Firefox中该事件会触发。这里只是提及一下,关系不大。

<label for="uploadedFile">Drag&drop or select a file to play:</label>
<input type="file" id="uploadedFile"></input>

 

当然,这里同时也把最后我们要画图用的canvas也一起放上去吧,后面就不用多话了。所以下面就是最终的HTML了,页面基本不会变,大量的工作是在JavaScript的编写上。

复制代码
<div id="wrapper"><div id="fileWrapper" class="file_wrapper"><div id="info">HTML5 Audio API showcase | An Audio Viusalizer</div><label for="uploadedFile">Drag&drop or select a file to play:</label><input type="file" id="uploadedFile"></input></div><div id="visualizer_wrapper"><canvas id='canvas' width="800" height="350"></canvas></div>
</div>
复制代码

 

再稍微写一点样式

复制代码
#fileWrapper {transition: all 0.5s ease;
}
#fileWrapper: hover {opacity: 1!important;
}
#visualizer_wrapper {text-align: center;
}
复制代码

 

向Visualizer对象的原型中新加一个方法,用于监听文件选择既前面讨论的onchange事件,并在事件中获取选择的文件。

复制代码
_addEventListner: function() {var that = this,audioInput = document.getElementById('uploadedFile'),dropContainer = document.getElementsByTagName("canvas")[0];//监听是否有文件被选中audioInput.onchange = function() {//这里判断一下文件长度可以确定用户是否真的选择了文件,如果点了取消则文件长度为0if (audioInput.files.length !== 0) {that.file = audioInput.files[0]; //将文件赋值到Visualizer对象的属性上that.fileName = that.file.name;that._start(); //获取到文件后,开始程序,这个方法会在后面定义并实现};};
}
复制代码

 

上面代码中,我们假设已经写好了一个进一步处理文件的方法_start(),在获取到文件后赋值给Visualizer对象的file属性,之后在_start()方法里我们就可以通过访问this.file来得到该文件了,当然你也可以直接让_start()方法接收一个file参数,但将文件赋值到Visualizer的属性上的好处之一是我们可以在对象的任何方法中都能获取该文件 ,不用想怎么用参数传来传去。同样,将文件名赋值到Visualizer的fileName属性当中进行保存,也是为了方便之后在音乐播放过程中显示当前播放的文件。

通过拖拽获取

我们把页面中的canvas作为放置文件的目标,在它身上监听拖拽事件'dragenter','dragover','drop'等。

还是在上面已经添加好的_ addEventListner方法里,接着写三个事件监听的代码。

复制代码
dropContainer.addEventListener("dragenter", function() {that._updateInfo('Drop it on the page', true);
}, false);
dropContainer.addEventListener("dragover", function(e) {e.stopPropagation();e.preventDefault();e.dataTransfer.dropEffect = 'copy'; //设置文件放置类型为拷贝
}, false);
dropContainer.addEventListener("dragleave", function() {that._updateInfo(that.info, false);
}, false);
dropContainer.addEventListener("drop", function(e) {e.stopPropagation();e.preventDefault();that.file = e.dataTransfer.files[0]; //获取文件并赋值到Visualizer对象that.fileName = that.file.name;that._start();
}, false);
复制代码

 

注意到上面代码中我们在'dragover'时设置文件拖放模式为'copy',既以复制的形式获取文件,如果不进行设置无法正确获取文件

然后在'drop'事件里,我们获得文件以进行一下步操作。

用FileReader读取文件为ArrayBuffer

下面来看这个_start()方法,现在文件得到 了,但首先需要将获取的文件转换为ArrayBuffer格式,才能够传递给AudioContext进行解码,所以接下来_start()方法中要干的事情就是实例化一个FileReader来将文件读取为ArrayBuffer格式。

复制代码
_start: function() {//read and decode the file into audio array buffervar that = this, //当前this指代Visualizer对象,赋值给that以以便在其他地方使用file = this.file, //从Visualizer对象上获取前面得到的文件fr = new FileReader(); //实例化一个FileReader用于读取文件fr.onload = function(e) { //文件读取完后调用此函数var fileResult = e.target.result; //这是读取成功得到的结果ArrayBuffer数据var audioContext = that.audioContext; //从Visualizer得到最开始实例化的AudioContext用来做解码ArrayBufferaudioContext.decodeAudioData(fileResult, function(buffer) { //解码成功则调用此函数,参数buffer为解码后得到的结果that._visualize(audioContext, buffer); //调用_visualize进行下一步处理,此方法在后面定义并实现}, function(e) { //这个是解码失败会调用的函数console.log("!哎玛,文件解码失败:(");});};//将上一步获取的文件传递给FileReader从而将其读取为ArrayBuffer格式fr.readAsArrayBuffer(file);
}
复制代码

 

注意这里我们把this赋值给了that,然后再 audioContext.decodeAudioData的回调函数中使用that来指代我们的Visualizer对象。这是因为作用域的原因。我们知道JavaScript中无法通过花括号来创建代码块级作用域,而唯一可以创建作用域的便是函数。一个函数就是一个作用域。函数内部的this指向的对象要视情况而定,就上面的代码来说,它是audioContext。所以如果想要在这个回调函数中调用Visualizer身上方法或属性,则需要通过另一个变量来传递,这里是that,我们通过将外层this(指向的是我们的Viusalizer对象)赋值给新建的局部变量that,此时that便可以传递到内层作用域中,而不会与内层作用域里面原来的this相冲突。像这样的用法在源码的其他地方也有使用,细心的你可以下载本文的源码慢慢研究。

所以,在 audioContext.decodeAudioData的回调函数里,当解码完成得到audiobuffer文件(buffer参数)后,再把audioContext和buffer传递给Visualizer的_visualize()方法进一步处理:播放音乐和绘制频谱图。当然此时_visualize()方法还没有下,下面便开始实现它。

创建Analyser分析器及播放音频

上面已经将获取的文件进行解码,得到了audio buffer数据。接下来是设置我们的AudioContext以及获取频谱能量信息的Analyser节点。向Visualizer对象添加_visualize方法,我们在这个方法里完成这些工作。

播放音频

首先将buffer赋值给audioContext。AudioContext只相当于一个容器,要让它真正丰富起来需要将实际的音乐信息传递给它的。也就是将audio buffer数据传递给它的BufferSource属性。

其实到了这里你应该有点晕了,不过没关系,看代码就会更明白一些,程序员是理解代码优于文字的一种生物。

var audioBufferSouceNode = audioContext.createBufferSource();
audioBufferSouceNode.buffer = buffer;

 

就这么两名,把音频文件的内容装进了AudioContext。

这时已经可以开始播放我们的音频了。

audioBufferSouceNode.start(0);

 

这里参数是时间,表示从这段音频的哪个时刻开始播放。

注意:在旧版本的浏览器里是使用onteOn()来进行播放的,参数一样,指开始时刻。

但此时是听不到声音的,因为还差一步,需要将audioBufferSouceNode连接到audioContext.destination,这个AudioContext的destination也就相关于speaker(扬声器)。

audioBufferSouceNode.connect(audioContext.destination);
audioBufferSouceNode.start(0);

 

此刻就能够听到扬声器传过来动听的声音了。

_visualize: function(audioContext, buffer) {var audioBufferSouceNode = audioContext.createBufferSource();audioBufferSouceNode.connect(audioContext.destination);audioBufferSouceNode.buffer = buffer;audioBufferSouceNode.start(0);
}

 

创建分析器

创建获取频谱能量值的analyser节点。

var analyser = audioContext.createAnalyser();

 

上面一步我们是直接将audioBufferSouceNode与audioContext.destination相连的,音频就直接输出到扬声器开始播放了,现在为了将音频在播放前截取,所以要把analyser插在audioBufferSouceNode与audioContext.destination之间。明白了这个道理,代码也就很简单了,audioBufferSouceNode连接到analyser,analyser连接destination。

audioBufferSouceNode.connect(analyser);
analyser.connect(audioContext.destination);

 

然后再开始播放,此刻所有音频数据都会经过analyser,我们再从analyser中获取频谱的能量信息,将其画出到Canvas即可。

假设我们已经写好了画频谱图的方法_drawSpectrum(analyser);

复制代码
_visualize: function(audioContext, buffer) {var audioBufferSouceNode = audioContext.createBufferSource(),analyser = audioContext.createAnalyser();//将source与分析器连接audioBufferSouceNode.connect(analyser);//将分析器与destination连接,这样才能形成到达扬声器的通路analyser.connect(audioContext.destination);//将上一步解码得到的buffer数据赋值给sourceaudioBufferSouceNode.buffer = buffer;//播放audioBufferSouceNode.start(0);//音乐响起后,把analyser传递到另一个方法开始绘制频谱图了,因为绘图需要的信息要从analyser里面获取this._drawSpectrum(analyser);
}
复制代码

 

绘制精美的频谱图

接下来的工作,也是最后一步,也就是实现_drawSpectrum()方法,将跟随音乐而灵动的柱状频谱图画出到页面。

绘制柱状能量槽

首先你要对数字信号处理有一定了解,吓人的,不了解也没多大关系。频谱反应的是声音各频率上能量的分布,所以叫能量槽也没有硬要跟游戏联系起来的嫌疑,是将输入的信号经过傅里叶变化得到的(大学里的知识终于还是可以派得上用场了)。但特么我知道这些又怎样呢,仅仅为了装逼显摆而以。真实的频谱图是频率上连续的,不是我们看到的最终效果那样均匀分开锯齿状的。

通过下面的代码我们可以从analyser中得到此刻的音频中各频率的能量值。

var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);

 

此刻array中存储了从低频0Hz到高频~Hz的所有数据。频率做为X轴,能量值做为Y轴,我们可以得到类似下面的图形。

你也可以上传文件自己看一下效果 http://sandbox.runjs.cn/show/1kn8nr4l 

所以,比如array[0]=100,我们就知道在x=0处画一个高为100单位长度的长条,array[1]=50,然后在x=1画一个高为50单位长度的柱条,从此类推,如果用一个for循环遍历array将其全部画出的话,便是你看到的上图。

采样

但我们要的不是那样的效果,我们只需在所有数据中进行抽样,比如设定一个步长100,进度抽取,来画出整个频谱图中的部分柱状条。

或者先根据画面的大小,设计好每根柱条的宽度,以及他们的间隔,从而计算出画面中一共需要共多少根,再来推算出这个采样步长该取多少,本例便是这样实现的。说来还是有点晕,下面看简单的代码:

var canvas = document.getElementById('canvas'),meterWidth = 10, //能量条的宽度gap = 2, //能量条间的间距meterNum = 800 / (10 + 2); //计算当前画布上能画多少条
var step = Math.round(array.length / meterNum); //计算从analyser中的采样步长

 

我们的画布即Canvas宽800px,同时我们设定柱条宽10px , 柱与柱间间隔为2px,所以得到meterNum为总共可以画的柱条数。再用数组总长度除以这个数目就得到采样的步长,即在遍历array时每隔step这么长一段我们从数组中取一个值出来画,这个值为array[i*step]。这样就均匀地取出meterNum个值,从而正确地反应了原来频谱图的形状。

复制代码
var canvas = document.getElementById('canvas'),cwidth = canvas.width,cheight = canvas.height - 2,meterWidth = 10, //能量条的宽度gap = 2, //能量条间的间距meterNum = 800 / (10 + 2), //计算当前画布上能画多少条ctx = canvas.getContext('2d'),array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / meterNum);计算从
analyser中的采样步长
ctx.clearRect(0, 0, cwidth, cheight); //清理画布准备画画
//定义一个渐变样式用于画图
gradient = ctx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(1, '#0f0');
gradient.addColorStop(0.5, '#ff0');
gradient.addColorStop(0, '#f00');
ctx.fillStyle = gradient;
//对信源数组进行抽样遍历,画出每个频谱条
for (var i = 0; i < meterNum; i++) {var value = array[i * step];ctx.fillRect(i * 12 /*频谱条的宽度+条间间距*/ , cheight - value + capHeight, meterWidth, cheight);
}
复制代码

 

使用requestAnimationFrame让柱条动起来

但上面绘制的仅仅是某一刻的频谱,要让整个画面动起来,我们需要不断更新画面,window.requestAnimationFrame()正好提供了更新画面得到动画效果的功能,关于requestAnimationFrame的使用及更多信息可以从我的上一篇博文<requestAnimationFrame,Web中写动画的另一种选择>中了解,这里直接给出简单改造后的代码,即得到我们要的效果了:跟随音乐而灵动的频谱柱状图。

复制代码
var canvas = document.getElementById('canvas'),cwidth = canvas.width,cheight = canvas.height - 2,meterWidth = 10, //能量条的宽度gap = 2, //能量条间的间距meterNum = 800 / (10 + 2), //计算当前画布上能画多少条ctx = canvas.getContext('2d');
//定义一个渐变样式用于画图
gradient = ctx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(1, '#0f0');
gradient.addColorStop(0.5, '#ff0');
gradient.addColorStop(0, '#f00');
ctx.fillStyle = gradient;
var drawMeter = function() {var array = new Uint8Array(analyser.frequencyBinCount);analyser.getByteFrequencyData(array);var step = Math.round(array.length / meterNum); //计算采样步长ctx.clearRect(0, 0, cwidth, cheight); //清理画布准备画画for (var i = 0; i < meterNum; i++) {var value = array[i * step];ctx.fillRect(i * 12 /*频谱条的宽度+条间间距*/ , cheight - value + capHeight, meterWidth, cheight);}requestAnimationFrame(drawMeter);
}
requestAnimationFrame(drawMeter);
复制代码

 

查看效果 http://sandbox.runjs.cn/show/q1ng0jgp

绘制缓慢降落的帽头

到上面一步,主要工作已经完成。最后为了美观,再实现一下柱条上方缓慢降落的帽头。

原理也很简单,就是在绘制柱条的同时在同一X轴的位置再绘制一个短的柱条,并且其开始和结束位置都要比频谱中的柱条高。难的地方便是如何实现缓慢降落。

首先要搞清楚的一点是,我们拿一根柱条来说明问题,当此刻柱条高度高于前一时刻时,我们看到的是往上冲的一根频谱,所以这时帽头是紧贴着正文柱条的,这个好画。考虑相反的情况,当此刻高度要低于前一时刻的高度时,下方柱条是立即缩下去的,同时我们需要记住上一时刻帽头的高度位置,此刻画的时候就按照前一时刻的位置将Y-1来画。如果下一时刻频谱柱条还是没有超过帽头的位置,继续让它下降,Y-1画出帽头。

通过上面的分析,所以我们在每次画频谱的时刻,需要将此刻频谱及帽头的Y值(即垂直方向的位置)记到一个循环外的变量中,在下次绘制的时刻从这个变量中读取,将此刻的值与变量中保存的上一刻的值进行比较,然后按照上面的分析作图。

最后给出实现的代码:

复制代码
_drawSpectrum: function(analyser) {var canvas = document.getElementById('canvas'),cwidth = canvas.width,cheight = canvas.height - 2,meterWidth = 10, //频谱条宽度gap = 2, //频谱条间距capHeight = 2,capStyle = '#fff',meterNum = 800 / (10 + 2), //频谱条数量capYPositionArray = []; //将上一画面各帽头的位置保存到这个数组ctx = canvas.getContext('2d'),gradient = ctx.createLinearGradient(0, 0, 0, 300);gradient.addColorStop(1, '#0f0');gradient.addColorStop(0.5, '#ff0');gradient.addColorStop(0, '#f00');var drawMeter = function() {var array = new Uint8Array(analyser.frequencyBinCount);analyser.getByteFrequencyData(array);var step = Math.round(array.length / meterNum); //计算采样步长ctx.clearRect(0, 0, cwidth, cheight);for (var i = 0; i < meterNum; i++) {var value = array[i * step]; //获取当前能量值if (capYPositionArray.length < Math.round(meterNum)) {capYPositionArray.push(value); //初始化保存帽头位置的数组,将第一个画面的数据压入其中};ctx.fillStyle = capStyle;//开始绘制帽头if (value < capYPositionArray[i]) { //如果当前值小于之前值ctx.fillRect(i * 12, cheight - (--capYPositionArray[i]), meterWidth, capHeight); //则使用前一次保存的值来绘制帽头} else {ctx.fillRect(i * 12, cheight - value, meterWidth, capHeight); //否则使用当前值直接绘制capYPositionArray[i] = value;};//开始绘制频谱条ctx.fillStyle = gradient;ctx.fillRect(i * 12, cheight - value + capHeight, meterWidth, cheight);}requestAnimationFrame(drawMeter);}requestAnimationFrame(drawMeter);
}
复制代码

 

Reference:

  1. A question about how to make an audio visualizer: http://stackoverflow.com/questions/3351147/html5-audio-visualizer
  2. Web audio API: http://www.html5rocks.com/en/tutorials/webaudio/intro/
  3. File reader in JavaScript: https://developer.mozilla.org/en-US/docs/Web/API/FileReader
  4. Local audio visualizer source code: http://cbrandolino.github.io/local-audio-visualizer/docs/local_audio_visualizer
  5. Audio context from MDN: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
  6. Window.requestAnimationFrame():https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame
  7. 3d visualizer with three.js : http://do.adive.in/music/
  8. A CodePen example: http://s.codepen.io/Wayou/fullpage/auCLE?
  9. Visualizer tutorial : http://www.smartjava.org/content/exploring-html5-web-audio-visualizing-sound
  10. Web audio examples from Google code : http://chromium.googlecode.com/svn/trunk/samples/audio/index.html

 

转自:https://www.cnblogs.com/Wayou/p/3543577.html#top

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Microsoft Visual Studio 2010(vs2010) 中文版安装

Microsoft Visual Studio 2010(vs2010) 中文版安装 日期&#xff1a;2019-05-12 时间&#xff1a;20:03:36 编辑&#xff1a;张国富 下载地址 基本简介 Microsoft Visual Studio&#xff08;vs2010是简称&#xff09;是微软公司推出的开发环境。visual studio 2010…

SpringBoot 之集成 Spring AOP

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 在开始之前&#xff0c;我们先把需要的jar包添加到工程里。新增Maven依赖如下&#xff1a; <dependency><groupId>org.spri…

数据库主键自增插入显示值

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主同意不得转载。 https://blog.csdn.net/nwsuaf2009012882/article/details/32703597 SQL Server 2008 数据库主键自增插入显示值 前几天在工作的时候遇到在删除数据库中表的数据的时候。删除之后&#xff0c;又一次…

Selenium自动化获取WebSocket信息

性能日志 ChromeDriver支持性能日志记录&#xff0c;您可以从中获取域“时间轴”&#xff0c;“网络”和“页面”的事件&#xff0c;以及指定跟踪类别的跟踪数据。启用性能日志 默认情况下不启用性能日志记录。因此&#xff0c;在创建新会话时&#xff0c;您必须启用它。 Desir…

《App后台开发运维与架构实践》第3章 App后台核心技术

2019独角兽企业重金招聘Python工程师标准>>> 3.1 用户验证方案 3.1.1 使用HTTPS协议 HTTPS协议是“HTTP协议”和“SSL/TLS”的组合。SSL&#xff08;Secure Sockets Layer&#xff09;&#xff0c;即安全套接层&#xff0c;是为了解决因HTTP协议是明文而导致传输内容…

IntelliJ IDEA 配置 JDK

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 提前安装jdk&#xff0c;配置环境变量 一、配置jdk 1、依次点开File -->Project Structure&#xff0c;点击左侧标签页&#xff0c…

xml编辑无提示?这么破!

在学习testng这个单元测试框架时&#xff0c;如果咱们碰到了编辑测试套件xml&#xff0c;不提示的情况&#xff08;有提示方便咱们学习&#xff0c;并且testng的测试套件定义必须按照他的dtd文件约束来&#xff09;&#xff0c;咱们可以按照下面的步骤去解决这个问题。 1.检查t…

navigator.geolocation的应用 - 将定位信息显示在百度地图上

在学习navigator.geolocation的时候&#xff0c;有一个实例是获取坐标后显示在谷歌地图上。众所周知&#xff0c;谷歌地图国内并不能直接访问&#xff0c;得用特殊手段&#xff0c;那我要测试的时候还要开着梯子挺麻烦的&#xff0c;想给别人用也得那个人能访问谷歌地图先。 地…

centos7 mysql数据库安装和配置

2019独角兽企业重金招聘Python工程师标准>>> 一、系统环境 yum update升级以后的系统版本为 [rootyl-web yl]# cat /etc/redhat-release CentOS Linux release 7.1.1503 (Core) 二、mysql安装 一般网上给出的资料都是 #yum install mysql #yum install mysql-serve…

【Quartz】Quartz概述及入门实例

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 Quartz 在开源任务调度框架中的翘首&#xff0c;它提供了强大任务调度机制&#xff0c;难能可贵的是它同时保持了使用的简单性。Quartz 允…

【Quartz】深入Job、JobDetail、JobDataMap、Trigger

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 Quartz API核心接口有&#xff1a; Scheduler – 与scheduler交互的主要API&#xff1b;Job – 你通过scheduler执行任务&#xff0c;你…

图形界面上机作业

1、 制作一个如图所示的界面&#xff08;使用FlowLayout布局&#xff09;&#xff0c;不要求实现功能。 2、设计一个用标签、文本行与按钮来登录的界面&#xff08;用GridLayout布局方式&#xff09;。如图所示。 转载于:https://www.cnblogs.com/quan-2723365710/p/10881691.h…

【Quartz】Spring3.2.9 + Quqrtz2.2.1 实现定时实例

一、工程创建 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1、新建一个工程&#xff0c;导入相应的包Spring3.2.9Quqrtz2.2.1commons-logging 集成起来比较简单,对线程无需任何理解&a…

第十二周作业

这个作业属于那个课程c语言这个作业要求在哪里https://edu.cnblogs.com/campus/zswxy/computer-scienceclass4-2018/homework/3236我在这个课程的目标是学习掌握单向链表&#xff0c;掌握二级指针的概念&#xff0c;以及指针数组这个作业在哪个具体方面帮助我实现目标前面3道题…

【Quartz】插件的使用

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 Quartz 框架提供了几种用于扩展平台能力的方式。通过使用各种 "钩子" (通常指的就是扩展点)&#xff0c;Quartz 变得很容易被扩…

自动化运维平台(前端vue)

前端的大体流程&#xff1a;首先是Login登录组件&#xff0c;当输入用户名和密码之后&#xff0c;发送post请求到后端&#xff0c;然后根据返回的数据的是否正常&#xff0c;如果正常&#xff0c;将返回的token以及用户名保存到sessionStorage中&#xff0c;并使用导航守卫进行…

Quartz 之入门示例

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 环境:XPMyeclipse6.5JDK1.6 quartz官网:http://www.quartz-scheduler.org/ 参考资料 1 Quartz任务调度快速入门 http://www.blogjava…

CSS3最颠覆性的动画效果,基本属性[3D]

和2D一样也是transform 即变形 1)rotateX rotateY rotateZ&#xff08;也可以用transform-origin来设置旋转中心点&#xff09; 2)透视(perspective) 给父亲加透视&#xff0c;透视就是模拟眼睛到物体的距离&#xff0c;近大远小&#xff0c;即数值越小&#xff0c;3D越明显 理…

解决报错 :A component required a bean of type ‘gentle.test.Show‘ that could not be found

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 启动工程失败&#xff0c;报错如题&#xff1a; A component required a bean of type gentle.test.Show that could not be found.…

解决.quartz.ObjectAlreadyExistsException: Unable to store Job : ‘jyGroup.jyJob‘, because one already

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 报错如题&#xff1a; 定时任务出现异常 &#xff1a; org.quartz.ObjectAlreadyExistsException: Unable to store Job : jyGroup.…