直播 弹幕

直播需求:

流媒体:流式传输方式在Internet上播放的媒体格式,音视频等多媒体文件。

流式传输:将多媒体文件经过特殊压缩后分成一个个压缩包,再由服务器向客户端连续,实时传送。用户不用等整个文件全部下载完毕后才能播放,而是只需要经过几秒或几十秒的启动延时即可边下载边播放。

常用的流媒体传输协议:HTTP渐进式下载和实时流媒体协议。

常用的直播流协议:


直播原理:

视频采集处理后,编码 然后流媒体封装 推流至流媒体服务器,然后用户端从服务器拉视频流过来解码播放。


客户端直播插件

hls.js:可以实现HTTP实时流媒体客户端的js库。包小,灵活 专业直播hls协议流,但是对于常规的通用性播放器没有封装好的UI,功能上需要自己调API,协议单一,只支持HLS。

video.js:基于H5的网络视频播放器,支持多种格式的流媒体播放,浏览器不支持时可以实现优雅降级。插件机制强大,兼容性好。但是包大,修改UI时需要通过插件实现。

vue-video-player:其实就是将video.js集成到了Vue中,在Vue项目中使用起来会更方便。

弹幕实现:

思路:横向分为11个channel,每一个channel有一个值控制当前channel是否可以往里面添加弹幕dom。循环这个channel数组,如果当前这个channel是不能添加的话,看看下一个channel是否能添加。所有的弹幕循环一次,如果有弹幕没有坐上车,则建一个新的数组,按顺序推入,下一趟车再坐。直到所有的弹幕都已经坐上车为止。

每一个弹幕dom创建的时候随机一个step值,在window.requestAnimationFrame这个异步方法里来改变该弹幕的right值。

step大小不一样,移动的速度也不一样。

当前channel如何判断是否可以添加下一个弹幕?

假设下一个弹幕的step是最大值,此处最大是5,计算这个弹幕和当前的弹幕从右边移动到左边需要的时间差。然后用这个时间乘以当前弹幕的step,那就是这个弹幕先领先这么多时间移动,下一条弹幕就在从右到左的过程中是不会追上当前弹幕。

当前弹幕领先移动这么多时间移动到计算的距离时,再把当前这个channel标识置为可添加。

                <template>

    <div ref="barrageContainer" class="barrage-content tw-absolute tw-top-0 tw-left-0 tw-w-full tw-h-77 tw-overflow-hidden">

        <div v-for="(item, index) in channel" :key="index" class="barrage-channel tw-h-7 tw-flex tw-items-center tw-relative"></div>

    </div>

</template>

<script lang="ts">

import {Component, Watch, Ref} from 'vue-property-decorator';

import Vue from '@ncfe/nc.vue';

@Component({

    name: 'BarrageRoll'

})

export default class BarrageRoll extends Vue {

    @Ref('barrageContainer') barrageContainer!: HTMLDivElement;

    private channel: any = new Array(11).fill(false);

    private barragePool: any = [];

    random(min, max) {

        return parseInt(Math.random() * (max - min) + min);

    }

    mounted() {

        this.barragePool = ['前方大量弹幕来袭,请做好准备!', '2333333', '主人下马客在船, 举酒欲饮无管弦。', '醉不成欢惨将别, 别时茫茫江浸月', '忽闻水上琵琶声, 主人忘归客不发。', '寻声暗问弹者谁? 琵琶声停欲语迟。', '移船相近邀相见, 添酒回灯重开宴。', '千呼万唤始出来, 犹抱琵琶半遮面。', '转轴拨弦三两声, 未成曲调先有情。', '弦弦掩抑声声思, 似诉平生不得志。', '低眉信手续续弹, 说尽心中无限事。', '轻拢慢捻抹复挑, 初为霓裳后六幺。', '大弦嘈嘈如急雨, 小弦切切如私语。', '嘈嘈切切错杂弹, 大珠小珠落玉盘。', '间关莺语花底滑, 幽咽泉流冰下难。', '冰泉冷涩弦凝绝, 凝绝不通声暂歇。', '别有幽愁暗恨生, 此时无声胜有声。', '银瓶乍破水浆迸, 铁骑突出刀枪鸣。', '曲终收拨当心画, 四弦一声如裂帛。', '东船西舫悄无言, 唯见江心秋月白。', '沉吟放拨插弦中, 整顿衣裳起敛容。', '自言本是京城女, 家在虾蟆陵下住。', '十三学得琵琶成, 名属教坊第一部。', '曲罢曾教善才服, 妆成每被秋娘妒。', '五陵年少争缠头, 一曲红绡不知数。', '钿头银篦击节碎, 血色罗裙翻酒污。', '今年欢笑复明年, 秋月春风等闲度。', '弟走从军阿姨死, 暮去朝来颜色故。', '门前冷落鞍马稀, 老大嫁作商人妇。', '商人重利轻别离, 前月浮梁买茶去。', '去来江口守空船, 绕船月明江水寒。', '夜深忽梦少年事, 梦啼妆泪红阑干。', '我闻琵琶已叹息, 又闻此语重唧唧。', '同是天涯沦落人, 相逢何必曾相识!', '我从去年辞帝京, 谪居卧病浔阳城。', '浔阳地僻无音乐, 终岁不闻丝竹声。', '住近湓江地低湿, 黄芦苦竹绕宅生。', '其间旦暮闻何物? 杜鹃啼血猿哀鸣。', '春江花朝秋月夜, 往往取酒还独倾。', '岂无山歌与村笛? 呕哑嘲哳难为听。', '今夜闻君琵琶语, 如听仙乐耳暂明。', '莫辞更坐弹一曲, 为君翻作《琵琶行》。', '感我此言良久立, 却坐促弦弦转急。', '凄凄不似向前声, 满座重闻皆掩泣。', '座中泣下谁最多? 江州司马青衫湿。', '2333333', '2333333', '2333333', '2333333', '浔阳江头夜送客, 枫叶荻花秋瑟瑟', '2333333', '2333333', '2333333', '2333333', '2333333', '2333333'];

    }

    @Watch('barragePool', {deep: true})

    barragePoolAction(list) {

        if (list.length) {

            this.checkList(list);

        }

    }

    checkList(list) {

        let remind: any = [];

        list.forEach(barrage => {

            let isShoot: boolean = false;

            if (barrage) {

                for (let index in this.channel) {

                    if (!this.channel[index]) {

                        this.channel[index] = true;

                        this.shoot(barrage, index);

                        isShoot = true;

                        break;

                    }

                }

                if (!isShoot) {

                    remind.push(barrage);

                }

            }

        });

        if (remind.length) {

            let timeout = setTimeout(() => {

                this.checkList(remind);

                clearTimeout(timeout);

            }, 500);

        }

    }

    shoot(barrage, index) {

        const oSpan = document.createElement('span');

        oSpan.innerHTML = barrage;

        oSpan.className = 'barrage-span';

        oSpan.dataset.begin = 'limit';

        const containerW = this.barrageContainer.getBoundingClientRect().width;

        const channel = document.querySelectorAll('.barrage-channel')[index];

        channel.appendChild(oSpan);

        const step = this.random(2, 5);

        const spanW = oSpan.getBoundingClientRect().width;

        let right = -spanW;

        window.requestAnimationFrame(this.move(oSpan, spanW, right, index, step, channel, containerW));

    }

    move(span, width, right, index, step, channel, containerW) {

        return () => {

            right += step;

            span.style.right = right + 'px';

            if (right < containerW) {

                window.requestAnimationFrame(this.move(span, width, right, index, step, channel, containerW));

                if (span.dataset.begin === 'limit' && right > this.diffDistance(step, containerW) && this.channel[index]) {

                    span.dataset.begin = 'notLimit';

                    this.channel[index] = false;

                }

            } else {

                channel.removeChild(span);

            }

        };

    }

    diffDistance(step, containerW) {

        return (containerW / step - containerW / 5) * step + 20;

    }

}

</script>

<style lang="scss">

.barrage-span {

    font-size: 20px;

    line-height: 28px;

    position: absolute;

    top: 0px;

    color: white;

    white-space: nowrap;

}

</style>


效果:


window.requestAnimationFrame(()=>{});  16ms多执行一次

requestAnimationFrame相对比setTimeout和setInterval的优势:

requestAnimationFrame会把每一帧的所有dom操作集中起来,在一次重绘或回流中完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说 这个频率为每秒60帧。

在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,更少的cpu,gpu和内存使用量。

停止requestAnimationFrame:

cancelAnimationFrame() 接收一个参数,requestAnimationFrame默认会返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351