教你让鬼畜视频更加鬼畜的方法!

1,012 阅读10分钟

大家好,我是b站编程up主冬灰条。

又到了编程up整活儿的时候了!

这次是要改变观看鬼畜视频的体验。

想要改变的体验有三个:

  1. 跟着音乐的节拍,让音谱出现页面上,达到音频可视化

  2. 让着弹幕跟着音乐节拍一起跳舞

  3. 背景色要有那种“闪耀的灯球”的感觉,像在ktv里一样

我完成了,最终效果非常的刺(bian)激(tai)。

为了方便复制代码,我写了一篇差不多的文章在掘金,可以自取代码:

可以看出来,前两个任务主要的突破口在于:

让浏览器监听音频变化

而且最好是直接监听扬声器的变化。

为了探究这个,我查了好多好多好多...(此处循环个21次“好多”)的资料,但查到的大多只有两个答案:“浏览器怎么可能做得到获取系统设备”和“浏览器获取麦克风的方法”。

一直查不到,毕竟这个需求可能比较小众,中文资料很少。后来好像是在一个国内可以访问到的英文技术论坛里查到了浏览器捕获屏幕窗口的接口。

捕获设备扬声器:getDisplayMedia!

我一看!噢!原来可以捕获窗口的时候,顺带捕获扬声器!

可算是查到了,于是就有了这段代码:

//浏览器捕获窗口时,监听扬声器变化的代码
navigator.mediaDevices.getDisplayMedia({
    audio: true,
    video: true,}).then((stream) => {
    // 创建音频对象
    let audioContext = new AudioContext();
    // 将麦克风的声音输入这个对象
    mediaStreamSource = audioContext.createMediaStreamSource(stream);
    // 创建一个音频分析对象,采样的缓冲区大小为4096,输入和输出都是单声道
    scriptProcessor = audioContext.createScriptProcessor(4096,1,1);
    // 将该分析对象与麦克风音频进行连接
    mediaStreamSource.connect(scriptProcessor);
    // 此举无甚效果,仅仅是因为解决 Chrome 自身的 bug
    scriptProcessor.connect(audioContext.destination);
    // 开始处理音频    scriptProcessor.onaudioprocess = function(e) {
        // 获得缓冲区的输入音频,转换为包含了PCM通道数据的32位浮点数组
        let buffer = e.inputBuffer.getChannelData(0);
        // 获取缓冲区中最大的音量值
        let maxVal = Math.max.apply(Math, buffer);
        // 显示音量值
        console.log(Math.round(maxVal * 100));
    };
})

但这段代码运行后,需要用户同意分享音频,授权给浏览器才行。

授权后,在16行代码,也就是sciptProcessor.onaudioprocess = function(e){}的作用域里面,就可以实时接收到音频变化的信息了!

这样一来,音频可视化,和控制弹幕大小就都容易了,只要在这个作用域里面对他们动手脚就可以了。

音频可视化

在网上音频可视化的资料有很多,我随便找了一个,但他们和我们的的区别是,他们是开发代码,我们是要往b站的页面上加代码:

好在代码很简单,只要往b站的页面加一个画布(canvas),加一个带有内容的样式标签(style)就可以了。

// 将画布元素插入页面中
// 获取页面本体
let body = document.getElementById('app');
// 新建画布元素
let canvas = document.createElement('canvas');
// 给画布元素绑定
idcanvas.id = 'canvas'
// 往页面本体里面插入画布
body.appendChild(canvas);
// 设置画布的宽,占满视觉窗口
canvas.width = '100vw';
// 设置画布的高,占满视觉窗口
canvas.height = '100vh';

// 往页面增加style标签
// 定义给页面新增的样式
let cssStr = `#canvas{
    position:fixed;
    pointer-events: none;
    top:0;
    left:0;
    opacity: 0.6;
    display: block;
    z-index:100;
}`;
// 新建一个Style元素
let style = document.createElement("style"); 
style.type = "text/css";
style.id = 'divStyle'; 
// 把css内容插入到style元素中style.innerHTML = cssStr;
// 往dom中增加style样式数据
document.getElementsByTagName("HEAD").item(0).appendChild(style);

当这些准备工作完成,把网上画音谱的代码,和监听扬声器音频的代码整合到一起了,可以结合备注看代码:

// 音频可视化
// 定义给页面新增的样式
let cssStr = `#canvas{
    position:fixed;
    pointer-events: none;
    top:0;
    left:0;
    opacity: 0.6;
    display: block;
    z-index:100;}`;
// 新建一个Style元素
let style = document.createElement("style"); 
style.type = "text/css";style.id = 'divStyle'; 
// 把css内容插入到style元素中
style.innerHTML = cssStr;
// 往dom中增加style样式数据
document.getElementsByTagName("HEAD").item(0).appendChild(style);
navigator.mediaDevices.getDisplayMedia({
    audio: true,
    video: true}).then((stream) => {
    let audioContext = new AudioContext();
    // 将麦克风的声音输入这个对象
    mediaStreamSource = audioContext.createMediaStreamSource(stream);
    // 创建一个音频分析对象,采样的缓冲区大小为4096,输入和输出都是单声道
    scriptProcessor = audioContext.createScriptProcessor(4096,1,1);
    // 将该分析对象与麦克风音频进行连接
    mediaStreamSource.connect(scriptProcessor);
    // 此举无甚效果,仅仅是因为解决 Chrome 自身的 bug
    scriptProcessor.connect(audioContext.destination);
    // 创建分析机     
    let analyser = audioContext.createAnalyser();
    // 媒体源与分析机连接
    mediaStreamSource.connect(analyser);
    // 将画布元素插入页面中
    // 获取页面本体
    let body = document.getElementById('app');
    // 新建画布元素
    let canvas = document.createElement('canvas');
    // 给画布元素绑定id
    canvas.id = 'canvas'
    // 往页面本体里面插入画布
    body.appendChild(canvas);
    // 设置画布的宽,占满视觉窗口
    canvas.width = '100vw';
    // 设置画布的高,占满视觉窗口
    canvas.height = '100vh';
    // 设置一些长啊,宽啊,高啊的常数,为一会儿画音谱做准备
    let ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight; 
    let oW = canvas.width; 
    let oH = canvas.height;
    let color1 = ctx.createLinearGradient(oW / 2, oH / 2 - 30, oW / 2, oH / 2 - 100); 
    let color2 = ctx.createLinearGradient(oW / 2, oH / 2 + 30, oW / 2, oH / 2 + 100);
    color1.addColorStop(0, '#000');
    color1.addColorStop(.5, '#069');
    color1.addColorStop(1, '#f6f');
    color2.addColorStop(0, '#000');
    color2.addColorStop(.5, '#069');
    color2.addColorStop(1, '#f6f');
    // 音频图的条数
    let count = 150;
    // 缓冲区:进行数据的缓冲处理,转换成二进制数据
    let voice = new Uint8Array(analyser.frequencyBinCount);
    // 开始处理音频
    scriptProcessor.onaudioprocess = function(e) {
        // 将当前的频率数据复制到传入的无符号字节数组中,做到实时连接
        analyser.getByteFrequencyData(voice);
        // 自定义获取数组里边数据的频步
        let step = Math.round(voice.length / count);
        // 开始画画
        ctx.clearRect(0, 0, oW, oH);
        for (let i = 0; i < count; i++) {
            let audioHeight = voice[step * i]*1.5;
            ctx.fillStyle = color1;  // 绘制向上的线条
            ctx.fillRect(oW / 2 + (i * 10), oH / 2, 7, -audioHeight);
            ctx.fillRect(oW / 2 - (i * 10), oH / 2, 7, -audioHeight);
            ctx.fillStyle = color2;  // 绘制向下的线条
            ctx.fillRect(oW / 2 + (i * 10), oH / 2, 7, audioHeight);
            ctx.fillRect(oW / 2 - (i * 10), oH / 2, 7, audioHeight);
        }
    };
})

会呼吸的弹幕

会跟着音乐节拍变大变小的弹幕,我把它叫做“会呼吸的弹幕”,不过分吧。

这个更简单了,直接在sciptProcessor.onaudioprocess = function(e){}里控制弹幕元素的style就可以了,经常看我视频的小伙伴,对捕获元素应该很熟悉,直接上代码:

// 改变弹幕大小
/* 
    count就是音乐可视化的音柱的数量,现在固定是150
    voice是一个音频数据数组,可能很长很长
    所以算出step,从voice的数组里面,抽出150个意思一下就行了
*/
let step = Math.round(voice.length / count);
// 根据音频分贝的大小,改变弹幕的大小和透明度
// 捕获弹幕组件的父元素(每个弹幕都是这个元素的子元素)
let danmaku = document.getElementsByClassName('bilibili-player-video-danmaku')[0];
// 开启一个循环
for(let i = 0;i<danmaku.children.length;i++){
    /*
        音频数据是一个数组,
        整个数组都会跟着音乐变化,
        这里随便娶一个靠150中间的数据,我取的是72
        然后再用数学公式调整一下大小
        让后让弹幕字体等于这个大小就行了
    */
    danmaku.children[i].style.fontSize = `${25+(voice[(step*72)]/2)}px` 
    // 改变透明度,原理同上
    danmaku.children[i].style.opacity = `${(210-(voice[(step*72)]))/210}`}

流彩的背景色

我乱起的名字,但其实就是渐变动画背景,这种轮子可太好找了。

刚刚已经增加了style,只要往style里面增加class样式。

然后往body上挂class就搞定了,ctrl+c,ctrl+v搞定的事儿,就直接贴完整代码:

// 定义给页面新增的样式
let cssStr = `#canvas{
    position:fixed;
    pointer-events: none;
    top:0;
    left:0;
    opacity: 0.6;
    display: block;
    z-index:100;}
.changeBack{
    background: linear-gradient(90deg, #ffc700 0%, #e91e1e 33%, #6f27b0 66%, #00ff88 100%);
    background-position: 100% 0;
    animation: bgSize 5s infinite ease-in-out alternate;
}
@keyFrames bgSize{
    0% {
        background-size: 300% 100%;
    }
    100% {
        background-size: 100% 100%;
    }}`;
// 新建一个Style元素
let style = document.createElement("style"); 
style.type = "text/css"; 
style.id = 'divStyle';
style.innerHTML = cssStr;
// 往dom中增加样式数据
document.getElementsByTagName("HEAD").item(0).appendChild(style);
// 给body披上流彩背景
document.getElementsByTagName('body')[0].classList.add('changeBack')
// 读取桌面的设备,这里主要需要获取音频
navigator.mediaDevices.getDisplayMedia({
    audio:true,
    video:true}).then(function(mediaStream) {
    // 创建音频对象
    let audioContext = new AudioContext();
    // 将麦克风的声音输入这个对象
    mediaStreamSource = audioContext.createMediaStreamSource(mediaStream);
    // 创建一个音频分析对象,采样的缓冲区大小为4096,输入和输出都是单声道
    scriptProcessor = audioContext.createScriptProcessor(4096,1,1);
    // 将该分析对象与麦克风音频进行连接
    scriptProcessor.connect(audioContext.destination);
    // 创建分析对象
    let analyser = audioContext.createAnalyser();
    // 让分析对象链接麦克风
    mediaStreamSource.connect(analyser);
    let voice = new Uint8Array(analyser.frequencyBinCount);
    // 将画布元素插入页面中
    let body = document.getElementById('app'); 
    let canvas = document.createElement('canvas');
    canvas.id = 'canvas'
    body.appendChild(canvas);
    // 为画音频可视化做数据准备
    canvas.width = '100vw';
    canvas.height = '100vh';
    let ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    let oW = canvas.width;
    let oH = canvas.height;
    let color1 = ctx.createLinearGradient(oW / 2, oH / 2 - 30, oW / 2, oH / 2 - 100);
    let color2 = ctx.createLinearGradient(oW / 2, oH / 2 + 30, oW / 2, oH / 2 + 100);
    color1.addColorStop(0, '#000');
    color1.addColorStop(.5, '#069');
    color1.addColorStop(1, '#f6f');
    color2.addColorStop(0, '#000');
    color2.addColorStop(.5, '#069');
    color2.addColorStop(1, '#f6f');
    // 设置音频可视化的数量
    let count = 150;
    // 监听音频的分贝变化,一旦变化就触发下面的函数
    scriptProcessor.onaudioprocess = function(e) {
        // 更新分析对象
        analyser.getByteFrequencyData(voice);
        /*
            count就是音乐可视化的音柱的数量,现在固定是150
            voice是一个音频数据数组,可能很长很长
            所以算出step,从voice的数组里面,抽出150个意思一下就行了
        */
        let step = Math.round(voice.length / count);
        // 根据音频分贝的大小,改变弹幕的大小和透明度 
        // 捕获弹幕组件的父元素(每个弹幕都是这个元素的子元素)
        let danmaku = document.getElementsByClassName('bilibili-player-video-danmaku')[0];
        // 开启一个循环
        for(let i = 0;i<danmaku.children.length;i++){
            /*
                音频数据是一个数组,
                整个数组都会跟着音乐变化,
                这里随便娶一个靠150中间的数据,我取的是72
                然后再用数学公式调整一下大小
                让后让弹幕字体等于这个大小就行了
            */
            danmaku.children[i].style.fontSize = `${25+(voice[(step*72)]/2)}px`
            // 改变透明度,原理同上
            danmaku.children[i].style.opacity = `${(210-(voice[(step*72)]))/210}`
        }
        // 根据音频分贝的大小,改变音频可视化的画布
        ctx.clearRect(0, 0, oW, oH);
        for (let i = 0; i < count; i++) {
            let audioHeight = voice[step * i]*1.5;
            ctx.fillStyle = color1;
            ctx.fillRect(oW / 2 + (i * 10), oH / 2, 7, -audioHeight);
            ctx.fillRect(oW / 2 - (i * 10), oH / 2, 7, -audioHeight);
            ctx.fillStyle = color2;
            ctx.fillRect(oW / 2 + (i * 10), oH / 2, 7, audioHeight);
            ctx.fillRect(oW / 2 - (i * 10), oH / 2, 7, audioHeight);
        }
    };
})

到这里就全部搞定了,可以复制代码。直接到浏览器(我用的是谷歌浏览器)的视频播放页面里,就能体验升级版鬼畜了。

刷新或者关闭页面,这个脚本也就自动失效了。

这里就有个问题:每次打开一个新的页面,都要复制代码太麻烦了。

如何保持这个脚本的效果呢?

所以我查资料学了一下,如何开发谷歌插件,然后把这个功能开发成了插件。

升级成插件

插件就比较简单了,直接进入浏览器的插件管理:

进入浏览器扩展程序

进入扩展程序页面

把我做好的扩展程序拽进去...

但是不行...它不让用。

那只能让大家换个方式了。直接开启开发模式,也就是右上角那个,开启开发模式:

然后我把我开发插件的文件夹,上传到百度网盘,大家自取:

链接:pan.baidu.com/s/1MjFyIWTj…

提取码:3zts  

(虽然我保证无毒无害,如果担心危险的,建议直接不要尝试就完事儿~)

下载后,把文件加直接往开发目录里拽。

成功啦!

然后再去随便打开一个特备好看的视频,比如我听说有个叫冬灰条的up主,视频就特别好看!

出现授权页面了!

然后按着我视频里面的用法,耍就行。

这次脚本怎么关呢?

两种都可以!

以上就是的全部内容啦!

搭配视频效果更佳,视频链接会在发布后贴上!

感谢支持和关注!希望大家喜欢我的视频,然后多多三连,我才能有动力做得更好!另外欢迎大家评论和私信和我交流呀!

我是冬灰条!会做好玩有趣的编程视频!欢迎关注我!

下回见!