精华内容
下载资源
问答
  • 小程序云开发实时弹幕功能
    2020-11-07 15:46:56

    因为要增加一下小程序的互动,或者说互相督促的作用吧,加个弹幕大家可以日常开撕,哈哈哈……效果如下,视频只能给url,就没弄了。
    在这里插入图片描述

    思路

    1、首先弹幕的滚动,我是想用css的动画来写,主要是其他的不会,菜是原罪。
    2、每个弹幕不能在同一轨道上显示,给个随机数给css动画top值,就能不重叠显示everyone的弹幕。
    3、小程序云还是比较容易上手的,将输入的弹幕传入云数据库,这里实时显示我取巧了,直接把你发射的弹幕先投屏到轨道上,但其他人看到你的弹幕要从云上拿数据。
    4、显示他人弹幕的话,主要担心数据量太大,我只拿本月的弹幕数据下来做遍历展示,当然做了个手动选择弹幕开启关闭。

    前端页面

    参考了一个博主的样式,但写了太久了我忘了是谁了……

    背景框

    <view class='launchGird'>
    	<!-- 背景框 -->
    	<view class='displayGroup'>
    		<view class='dmGroup' wx:for="{{ dmData }}" wx:key="id"  style="top:{{ item.top }}%; animation: dmAnimation {{item.time}}s linear {{ index*3 }}s infinite; ">
    

    弹幕文字框

    其实我没写啥框的样式,哈哈哈哈

    		<!-- 弹幕文字框 -->
    			<view class='dmItem'>
                    <view class='dm'>
                    	<text class='content'>{{ item.content }}</text>
                    </view>
                </view>
            </view>
    

    自定义新轨道

            <!-- 自定义新轨道 -->
          <view class='dmGroup' wx:for="{{ dmData1 }}" wx:key="id"  style="top:{{ item.top }}%; animation: dmAnimation {{item.time}}s linear {{ index*3 }}s infinite; ">
            <view class='dmItem'>
              <view class='dm'>
                <text class='content'>{{ item.content }}</text>
              </view>
            </view>
          </view>
          <!-- 背景图 -->
          <view class='focus'>
            <image src='cloud://xiaoxiong-6p4ms.7869-xiaoxiong-6p4ms-1301094256/swiper/scut11.jpg' class='img' mode='aspectFix'></image>
          </view>
          <view class='shadowOverlay'></view>
      </view>
    

    弹幕输入框

    <!-- 弹幕输入框 -->
      <view class="barrage-inputText">
        <view class="barrage-input">
           <input bindinput="getvalue" value="{{baseData1}}" maxlength="100" placeholder="发个弹幕试试?"/>
        </view>
        <view class="barrage-shoot">
             <button size="mini" bindtap="bind_shoot">Send</button>
         </view>
         <switch id="switch_" bindchange="barrageSwitch"/>
       		<text>弹幕</text>
        </view>
     </view>
    

    CSS样式

    .launchGird{ padding: 20rpx; position: relative; height:100%;}
    .displayGroup{ background: #eee; border: 20rpx solid #fff; box-shadow: 0 0 10rpx #eee; border-radius: 10rpx; margin-bottom: 40rpx; height: 420rpx; position: relative; overflow: hidden;}
    .dmGroup{ position: absolute; top:140rpx; left: 100%; z-index: 10; animation-timing-function: linear; animation-fill-mode: none; transform: translateZ(0);  white-space: nowrap; height: 60rpx; }
    .dmItem{ display: inline-flex; margin-right: 60rpx; white-space: nowrap;  }
    .dmItem .dm{ display: inline-flex; vertical-align: middle; align-items: center; position: relative; height: 50rpx; line-height: 50rpx; padding: 0 15rpx 0 5rpx; background: rgba(0,0,0,.5);  border-radius: 25rpx; overflow: hidden; font-size: 24rpx; color: #fff; }
    .dmItem .dm .content{display: inline-block; max-width: 440rpx; height: 50rpx; line-height: 50rpx; margin-right: 10rpx; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
    .focus{ position: absolute; left: 0; top: 0;  z-index: 2; width: 100%; height: 100%;  border-radius: 10rpx;  overflow: hidden; }
    .shadowOverlay { position: absolute; top: 0; left: 0; z-index: 5; width: 100%; height: 100%; overflow: hidden;  background-image: linear-gradient(0deg, rgba(0,0,0,.8) 0%, rgba(0,0,0,.3) 50%,rgba(0,0,0,.1) 100%); }
    .img{ width: 100%; height: 100%; border: 0; }
    /* 发射框 */
    .barrage-inputText{
       /* position: absolute; */
       display: flex;
       background-color: rgb(219, 219, 219);
       width: 100%;
       height: 80rpx;
       flex-direction: row;
       nav-index: 2;
       justify-content: center;
       align-items: center;
       box-shadow:0.5px 0.5px 3px #ffffff;
    }
    .barrage-input{
       background-color: rgb(243, 255, 239);
       width: 60%;
       height: 30px;
    }
    .barrage-shoot{
       display:flex;
       margin-left: 10px;
       width: 25%;
       height: 30px;
       justify-content: center;
    }
    .shoot{
       width: 100%;
       color: black;
    }
    

    JS以及云存储/读取

    1、首先我们要获取用户输入的内容,暂时存储到data里的baseData1,因为用户每次只有一句话所以给个字符串就OK

    const app = getApp()
    const DB = wx.cloud.database().collection('scut-dm')//这里你写自己的数据库名字
    var util = require('../../utils/util.js')
    var dayTime = util.formatTime(new Date());
    
    Page({
      data: {
        baseData: [],//历史弹幕(云加载)
        baseData1:"",//即时输入内容
        dmData: [],//加载后处理数据
        dmData1:[],//键盘输入传入
      },
      //获取来自input的内容
      getvalue(e){
        this.setData({
          baseData1:e.detail.value
        })
        // console.log(e.detail.value)
      },
    

    2、发射弹幕&将弹幕内容传入云

    //发射弹幕,处理input的内容加时间和top变成数组,再交给页面遍历
      bind_shoot: function () {
        // 将即时DM传入云
        DB.add({
          data: {
            content: this.data.baseData1,
            date: dayTime
          },
          success(res) {
            console.log('添加成功', res)
          },
          fail(res) {
            console.log("添加失败", res)
          }
        })
        //即使发送弹幕
        const dmArr = [];
        const _b = this.data.baseData1;
        for (let i = 0; i < 1; i++) {
          const time = Math.floor(Math.random() * 10);
          const _time = time < 6 ? 6 + i : time + i;//这是弹幕从左到右的随机时间
          const top = Math.floor(Math.random() * 80) + 2;//这里是给每个弹幕一个随机高度,避免重叠显示
          const _p = {
            content: _b,
            top,
            time: _time,
          };
          dmArr.push(_p);
        }
        this.setData({
          dmData1: dmArr,//将文本,位置,运动速度给dmData1给前端遍历
          baseData1:""
        });
      //  console.log("啦啦啦啦", dmArr)
      },
    

    3、调用云历史弹幕
    这里我通过当前年月字段来匹配本月的弹幕,所以对当前时间做分割(其实也是自己nc数据库没做索引搞得匹配麻烦的一批,当然对各位大佬来说怎么写都很简单)拿到的数据存到baseData数组

    // 调用历史弹幕
        let arr =""
        let arrf = ""
        let str = dayTime
        arr=str.split("/")//分离成数组["2020","08","27 时分秒"]
        // arrf = arr[2].split(" ")//提取第三个数组分离成["27","时分秒"]
        // arr.splice(2,2,arrf[0])取出27并添加到arr第三个位置
        // const arr1 = arr.join(",").replace(/,/g, "")替换所有得都好成/
        arr.splice(2,2)
        arrf = arr.join(",").replace(/,/g, "/")
        console.log("这是什么",arrf)
        // console.log("这是什么", arr1)
        DB.where({
          // name: _name,
          date: wx.cloud.database().RegExp({
            regexp: arrf,
            options: 'i',
          })
        }).get({
          success(res) {
            console.log("模糊查询成功", res)
            that.setData({
              baseData: res.data
            })
          },
          fail() {
            console.log("获取失败", res)
          }
        });
        // wx.cloud.callFunction({
        //   name: "getdm2",
        //   success(res) {
        //     console.log("获取成功dm-------", res.result.data)
        //     that.setData({
        //       baseData: res.result.data
        //     })
        //   },
        //   fail(res) {
        //     console.log("获取失败dm", res)
        //   }
        // })
      },
    

    4、处理历史弹幕位置,就是将上面云拿到的数据处理遍历。其实这个函数和上面实时的函数是一样的,要是想省略可以直接传参,但……我还是选择了ctrl+c/ctrl+v,没有为什么,就是懒。

    // 处理弹幕位置
      setDM: function () {
        const dmArr = [];
        const _b = this.data.baseData;//导入了来自app.js的所有弹幕消息
        for (let i = 0; i < _b.length; i++) {
          const time = Math.floor(Math.random() * 10);
          const _time = time < 6 ? 6 + i : time + i;
          const top = Math.floor(Math.random() * 80) + 2;
          const _p = {
            content: _b[i].content,
            top,
            time: _time,
          };
          dmArr.push(_p);
        }
        this.setData({
          dmData: dmArr
        });
      },
    

    5、判断用户是否打开弹幕开关

     //判断是否打开弹幕
      barrageSwitch(e) {
        if (e.detail.value) {
          this.setDM()
        }
        else{
          this.setData({
            dmData:[]
          })
        }
      },
    

    大概就是这么些东西,挺简单的。

    更多相关内容
  • 带你实现完整的视频弹幕系统

    千次阅读 2021-01-09 22:37:36
    本文字数:6244字预计阅读时间:35分钟+介绍弹幕诞生于日本的视频平台,后来被B站这种短视频平台引入到国内,并在国内发展壮大。后来逐渐被长视频平台所接受,现在视频相关的应用基本上都会有...

    本文字数:6244

    预计阅读时间:35分钟


    介绍

    弹幕诞生于日本的视频平台,后来被B站这种短视频平台引入到国内,并在国内发展壮大。后来逐渐被长视频平台所接受,现在视频相关的应用基本上都会有弹幕。

    但是长视频弹幕和B站这类的短视频弹幕还不太一样,短视频平台有自己特有的弹幕文化,所以弹幕更注重和用户的互动。长视频平台还是以看剧为主,弹幕类似于评论的功能,所以不能影响用户看剧,弹幕不能太密集,而且相互之间最好不要有遮盖,否则会对视频内容会有比较明显的影响。

    本篇文章主要从长视频平台的角度来讲弹幕的实现原理,但其实短视频平台的弹幕也是同样的原理,区别在于短视频可能弹幕种类会多一些。

    技术实现

    画布

    以我公司应用为例,有iPhoneiPad两个平台,在iPhone平台上有横竖屏的概念,都需要展示弹幕。在iPad上有大小屏的概念,也需要都展示弹幕。弹幕的技术方案肯定是两个平台用一套,但需要考虑跨不同设备和屏幕的情况。

    所以,对于这个问题,我通过画布的概念来解决通用性的问题。画布并不区分屏幕大小和比例的概念,只是单纯的用来展示弹幕,并不处理其他业务逻辑,通过一个Render类来控制画布的渲染。对于不同设备上的差异,例如iPad字体大一些,iPhone字体小一些这种情况,通过config类来进行控制,画布内部不做判断。

    小屏上画布会根据比例少展示一些,大屏上则多展示一些。字体变大画布也会根据比例和左右间距进行控制,保证展示比例是对的,并且在屏幕宽高发生改变后,自动适应新的尺寸,不会出现弹幕衔接断开的问题,例如iPad上大小屏切换。外部在使用时,只需要传入一个frame即可,不需要关注画布内部的调整。

    弹幕轨道

    从屏幕上来看,可以看到弹幕一般都是一行一行的。为了方便对弹幕视图进行管理,以及后续的扩展工作,我对弹幕设计了“轨道”的概念。每一行都是一个轨道,对弹幕进行横向的管理,这一行包括速度、末端弹幕、高度等参数,这些参数适用于这一行的所有弹幕。轨道是一个虚拟的概念,并没有对应的视图。

    轨道有对应的类来实现,类中会包含一个数组,数组中有这一行所有的弹幕。这个思路有点像玩过的一款游戏-节奏大师,里面也有音乐轨道的概念,每个轨道上对应不同速度和颜色的音符,音符数量也是不固定的,根据节奏来决定。

    轨道还有一个好处在于,对于不同速度的弹幕比较好控制。例如腾讯视频的弹幕其实是不同速度的,但是你仔细观察的话,可以发现他们的弹幕是“奇偶行不同速”,也就是奇数行一个速度,偶数行一个速度,让人从感官上来觉得所有弹幕的速度都不一样。如果通过轨道的方式就很好实现,不同的轨道根据当前所在行数,对发出的弹幕设置不同的速度即可。

    有时候看视频过程中会从右侧出现一条活动弹幕,可能是视频中的梗,也可能是类似于广告的互动。但是活动弹幕出现时一般是单行清屏的,也就是和普通弹幕是互斥的,展示活动弹幕的时候前后没有普通弹幕。这种通过轨道的方式也比较好实现,每条弹幕都对应一个时间段,根据活动弹幕的时间和速度,将活动弹幕展示的前后时间,将这段时间轨道暂时关闭,只保留活动弹幕即可。

    轮询

    每条弹幕都对应着一个展示时间,所以需要每隔一段时间就找一下有没有需要展示的弹幕。我设计的方案是通过轮询,来驱动弹幕展示。

    通过CADisplayLink来进行轮训,将frameInterval设置为60,即每秒轮询一次。在轮询的回调中查找有没有要展示的弹幕,有的话就从上到下查找每条轨道,某条轨道有位置可以展示的话就交给这条轨道展示,如果所有轨道都有正在展示的弹幕,则将此条弹幕丢弃。是否有位置是根据屏幕最右侧,最后一条弹幕是否已经展示完全,并且后面有空余位置来决定的。

    对于取数据的部分,数据和视图的逻辑是分离的,相互之间并没有耦合关系。取数据时只是从一个很小的字典中,根据时间取出所用的弹幕数据,并转化为model。字典的数据很少,最多十秒的数据,而且这里并不会接触到读数据库的操作,也没有网络请求的逻辑,这些都是独立的逻辑,后面会讲到。

    弹幕视图

    经常看视频的同学应该会知道,弹幕的展示形式有很多,有带明星头像的、有带点赞数的、带矩形背景色的,很多种展示形态。为了更好的对视图进行组织,所以我采用的就是很普通的UIView的展示形式,并没有为了性能去做很复杂的渲染操作。

    UIView的好处主要就是方便做布局和子视图管理,但在屏幕上做动画时,是对CALayer进行渲染的。也就是说UIView就是用来做视图组织,并不会直接参与渲染,这也符合苹果的设计理念。

    复用池

    弹幕是一个高频使用的控件,所以不能一直频繁创建,以及添加和移除视图,会对性能有影响。所以就像很多同学设计的模块一样,我也引入了缓存池的概念,我这里叫复用池。

    弹幕复用池和UITableView的复用池类似,离开屏幕的弹幕会被放在复用池中等待复用,下次直接从复用池中取而不重新创建。弹幕视图做的工作就是接收新的model对象,并根据弹幕类型进行不同的视图布局。

    并且弹幕只会在创建时被addSubview一次,当弹幕离开屏幕不会被从父视图移除,这样弹幕从复用池中取出时也不需要被addSubview。当动画执行完成后,弹幕就直接留在动画结束的位置,下次做动画时弹幕会自动回到fromValue的位置。实际上视图结构就如上图所示,灰色区域就是可视区域。

    系统弹幕

    在视频刚开始时会有引导信息,比如引导用户发弹幕,或者提示弹幕有多少条,这个我们叫做系统弹幕。系统弹幕一般是展示到屏幕中间时,才开始展示后续弹幕。但是要精确的计算到弹幕到达屏幕中间,然后再展示后续弹幕,这种的采用清除前后特定时间段的弹幕就不太精确,所以我们采用的是另一套实现方案。

    系统弹幕的实现是通过一个更高精度的CADisplayLink进行轮询检测,也就是把frameInterval设置的更小,我这里设置的是10,也就是每秒检测六次。但是进行检测时不能直接用CALayer进行判断,需要使用presentationLayer也就是屏幕上正在展示的layer进行检测,通过这个layer获取到的frame和屏幕上显示的才是一致的。

    这里简单介绍一下CALayer的结构,我们都知道UIView是对CALayer的一层封装,实际上屏幕上的显示都是通过layer来实现的,而layer本身也分为以下三层,并有不同的功能。

    • presentationLayer,其本身是当前帧的一个拷贝,每次获取都是一个新的对象,和动画过程中屏幕上显示的位置是一样的。

    • modelLayer,表示layer动画完成后的真实值,如果打印一下modelLayerlayer的话,发现二者其实是一个对象。

    • renderLayer,渲染帧,应用程序会根据视图层级,构成由layer组成的渲染树,renderLayer就代表layer在渲染树中的对象。

    炫彩弹幕

    在播放弹幕的过程中,我们可以看到有渐变颜色的弹幕,我们叫做“炫彩弹幕”。这种弹幕有一个很明显的特征,就是其颜色是渐变的。这时候要考虑性能的问题,因为播放高清视频时本身性能消耗就很大,在弹幕量比较大的情况下,会造成更多的性能消耗,所以减少性能消耗就是很重要的,渐变弹幕可能会使性能消耗加剧。

    对于渐变文字,一般都是通过mask的方式实现,下面放一个CAGradientLayer做渐变,上面盖一个文字的layer。但是这种会触发离屏渲染,会导致性能下降,并不能用这种方案。经过我们的尝试,决定用设置渐变文字颜色的方式解决。

    CGFloat scale = [UIScreen mainScreen].scale;
    UIGraphicsBeginImageContextWithOptions(imageSize, NO, scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGColorSpaceRef colorSpace = CGColorGetColorSpace([[colors lastObject] CGColor]);
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)ar, NULL);
    CGPoint start = CGPointMake(0.0, 0.0);
    CGPoint end = CGPointMake(imageSize.width, 0.0);
    CGContextDrawLinearGradient(context, gradient, start, end, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    CGGradientRelease(gradient);
    CGContextRestoreGState(context);
    UIGraphicsEndImageContext();
    

    实现方式就是先开辟一个上下文,用来进行图片绘制,随后对上下文进行一个渐变的绘制,最后获取到一个UIImage,并将图片赋值给UILabeltextColor即可。

    从离屏检测来看,并未发生离屏渲染,fps也始终保持在一个很高的水平。

    暂停和开始

    弹幕是随视频播放和暂停的,所以需要对弹幕提供暂停和继续的支持,对于这块我采用的CAMediaTiming协议来处理,可以通过此协议对动画的过程进行控制。

    代码中加0.05是为了避免弹幕在暂停时导致的回跳,所以加上一个时间差。具体原因是因为通过convertTime:fromLayer:方法计算得到的时间,和屏幕上弹幕的位置依然存在一个微弱的时间差,而导致渲染时视图位置发生回跳,这个0.05是一个实践得来的经验值。

    - (void)pauseAnimation {
        // 增加判断条件,避免重复调用
        if (self.layer.speed == 0.f) {
            return;
        }
        CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
        self.layer.speed = 0.f;
        self.layer.timeOffset = pausedTime + 0.05f;
    }
    
    - (void)resumeAnimation {
        // 增加判断条件,避免重复调用
        if (self.layer.speed == 1.f) {
            return;
        }
        CFTimeInterval pausedTime = self.layer.timeOffset;
        self.layer.speed = 1.0;
        self.layer.timeOffset = 0.0;
        self.layer.beginTime = 0.0;
        CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
        self.layer.beginTime = timeSincePause;
    }
    

    CAMediaTiming协议是用来对动画过程控制的一个协议,例如通过CoreAnimation创建的动画,CALayer遵守了这个协议。这样如果需要对动画进行控制的话,不需要引用一个CABasicAnimation对象,然后再修改动画属性这种方式对动画流程进行控制,只需要直接对layer的属性进行修改即可。

    下面是CAMediaTiming协议中一些关键的属性,在上文中也用到了其中的部分属性。

    • beginTime,动画开始时间,可以控制动画延迟展示。一般是一个绝对时间,为了保证准确性,最好先对当前layer进行一个转换,延迟展示在后面加对应的时间即可。

    • duration,动画结束时间。

    • speed,动画执行速度,默认是1。动画最终执行时间=duration/speed,也就是duration是3秒,speed是2,最终动画执行时间是1.5秒。

    • timeOffset,控制动画进程,主要用来结合speed来对动画进行暂停和开始。

    • repeatCount,重复执行次数,和repeatDuration互斥。

    • repeatDuration,重复执行时间,如果时间不是duration的倍数,最后一次的动画会执行不完整。

    • autoreverses,动画反转,在动画执行完成后,是否按照原先的过程反向执行一次。此属性会对duration有一个叠加效果,如果duration是1s,autoreverses设置为YES后时间就是2s。

    • fillMode,如果想要动画在开始时,就停留在fromValue的位置,就可以设置为kCAFillModeBackwards。如果想要动画结束时停留在toValue的位置,就设置为kCAFillModeForwards,如果两种都要就设置为kCAFillModeBoth,默认是kCAFillModeRemoved,即动画结束后移除。

    发送弹幕

    插入弹幕

    现在弹幕一般都会结合剧中主角,以及各种文字颜色让你去选择,通过这些功能也可以带来一部分付费用户。当发送一条弹幕时,会从上到下查找轨道,查找轨道时是通过presentationLayer来进行frame的判断,如果layer的最右边不在屏幕外,并且距离右侧屏幕还有一定空隙,项目中写的是10pt,则表示有空位可以插入下一条弹幕,这条弹幕会被放在这条轨道上。

    如果当前轨道没有空位置,则从上到下逐条查找轨道,直到找到有空位的轨道。如果当前屏幕上弹幕较多,所有轨道都没有空位,则这一条弹幕会被抛弃。

    如果是自己发的弹幕,这个是必须要展示出来的,因为用户发的弹幕要在界面上给用户一个反馈。对于自己发的弹幕,会有一个插队操作,优先级比其他弹幕都要高。自己发的弹幕并不入本地数据库,只是进行一个网络请求传给服务器,以及在界面上进行展示。

    选择角色

    在上面的图片中可以看到,文本之前会有角色和角色名,这些都是独立于输入文字之外的。用户如果删除完输入的文字之后,再点击删除要把角色也一起删除掉。输入框页面构成是一个UITextField,左边的角色头像和角色名是一个自定义View,被当做textFieldleftView来展示。如果删除的话就是将leftViewnil即可。

    问题在于,如果使用UIControlEventEditingChanged的事件,只能获取到文本发生变化时的内容,如果输入框的文字已经被删完,而角色是一个leftView,但由于文本已经为空,则无法再获取到删除事件,也就不能把角色删除掉。

    对于这个问题,我们找到了下面的协议来实现。UITextField遵守UITextInput协议,但UITextInput协议继承自UIKeyInput协议,所以也就拥有下面两个方法。下面两个方法分别在插入文字,以及点击删除按钮时调用,即使文本已经为空,依然可以收到deleteBackward的回调。在这个回调里就可以判断文本是否为空,如果为空则删除角色即可。

    @protocol UIKeyInput <UITextInputTraits>
    @property(nonatomic, readonly) BOOL hasText;
    - (void)insertText:(NSString *)text;
    - (void)deleteBackward;
    @end
    

    弹幕设置

    参数调整

    弹幕一般都不是一种形态,很多参数都是可以调整的,对于iPhoneiPad两个平台参数还不一样,调整范围也不一样。这些参数肯定是不能放在业务代码里进行判断的,这样各种判断条件散落在项目中,会导致代码耦合严重。

    对于这个问题,我们的实现方式是通过BarrageConfig来区分不同平台,将两个平台的数值差异都放在这个类中。业务部分直接读取属性即可,不需要做任何判断,包括退出进程的持久化也在内部完成,这样就可以让业务部分使用无感知,也保证了各个类中的数值统一。

    当有任何参数的改动,都可以对BarrageConfig进行修改,然后调用RenderlayoutBarrageSubviews进行渲染即可。因为调整参数之后,屏幕上已经显示的弹幕也需要跟着变,而且变得过程中还是在动画执行过程中,动画执行不能断掉,所以对动画的处理就很重要。这部分处理起来比较复杂,就不详细讲了。

    点赞

    弹幕还会有点赞和长按的功能,点赞一般是点击屏幕然后出现一个选择视图,点击点赞后有一个动画效果。长按就是选中一个弹幕,识别到手势长按之后,右侧出现一个举报页面。

    这两个手势我用taplongPress两个手势来处理,并给longPress设置了一个0.2s的识别时间,将这两种手势的识别交给系统去做,这样也比较省事。

    这两个手势都加到Render上,而不是每个弹幕视图对应一个手势,这样管理起来也比较简单。这样在手势识别时,就需要先找到手势触摸点,再根据触摸点查找对应的弹幕视图,查找的时候依然通过presentationLayer来查找区域,而不能用视图做查找。

    - (void)singleTapHandle:(UITapGestureRecognizer *)tapGestureRecognizer {
        CGPoint touchLocation = [tapGestureRecognizer locationInView:self.barrageRender];
        __block BOOL barrageLiked = NO;
        weakifyself;
        [self enumerateObjectsUsingBlock:^(SVBarrageItemLabelView *itemLabel, NSUInteger index, BOOL *stop) {
            strongifyself;
            if ([itemLabel.layer.presentationLayer hitTest:touchLocation] && barrageLiked == NO) {
                barrageLiked = YES;
                [self likeAction:itemLabel withTouchLocation:touchLocation];
                *stop = YES;
            }
        }];
    }
    

    弹幕广告

    广告

    对于这么好的一个展示位置,广告部门必然不会放过。在视频播放过程中,会根据金主爸爸投放要求,在指定的时间展示一个广告弹幕,并且这个弹幕的形态还是不固定的。也就是说大小、动画形式都不能确定,而且这条弹幕还要在最上层展示。

    对于这个问题,我们采用的方案是,给广告专门留了一个视图,视图层级高于Render,在初始化广告SDK的时候传给SDK,这样就把广告弹幕的控制交给SDK,我们不做处理。

    图层管理

    播放器上存在很多图层,播控、弹幕Render、广告之类的,看得到的和看不到的有很多。对于这个问题,播放器创建了一个继承自NSObject的视图管理器,这个视图管理器可以对视图进行分层管理。

    播放器上的视图,都需要调用指定的方法,将自己加到对应的图层上,移除也需要调用对应的方法。当需要调整前后顺序时,修改定义的枚举即可。

    数据分离

    前面一直说的都是视图的部分,没有涉及数据的部分,这是因为UI和数据其实是解耦和的,二者并没有强耦合,所以可以单独拿出来讲。数据部分的设计,类似于播放器的local server方案,将请求数据到本地,和从本地读取数据做了一个拆分。

    请求数据

    弹幕数据量比较大,肯定是不能一次都请求下来的,这样很容易造成请求失败的情况。所以这块采取的是五分钟一个分片数据,在当前的五分钟弹幕快播完的前十秒,开始请求下一个时间段的弹幕。如果拖动进度条,则拖动完成后开始请求新位置的弹幕。在每次请求前都会查一下库,数据是否已存在。

    请求数据由业务部分驱动,请求数据后并不会直接拿来使用,而是存入本地数据库,这部分比较像服务器往本地写ts分片的操作。数据库存储的部分,推荐使用WCDB,弹幕这块主要都是批量数据处理,而WCDB对于批量数据的处理,性能高于FMDB

    取数据

    取数据同样由业务层驱动,为了减少频繁进行数据库读写,每隔十秒钟进行一次数据库批量读取,并转换为model返回给上层。弹幕模块在内存中维护了一个字典,字典以时间为key,数组为value,因为同一时间可能会有多条弹幕。

    从数据库批量获取的数据会被保存到字典中,上层业务层在使用数据时,都是通过字典来获取数据,这样也实现了数据层和业务层的一个解耦和。上层业务层每隔一秒从字典中读取一次数据,并通过数据找到合适的轨道,将数据传给合适的轨道来处理。

    弹幕防挡探索

    现在很多视频网站都上线了弹幕防遮挡方案,对于视频中的人物,弹幕会在其下方展示,而不会遮挡住人物。还有的应用针对弹幕遮挡进行了新的探索,即成为付费会员后,可以选择只有自己喜欢的爱豆不被遮挡,其他人依然被遮挡。

    语义分割

    根据业务场景我们分析,首先需要把人像部分分割出来,获取到人像的位置之后才能做后续的操作。所以人像分割的部分采取语义分割的方式实现,提前对视频关键帧进行标注,这个工作量是很庞大的,所以需要一个专门的标注团队去完成。根据标注后的模型,通过机器学习的方式,让计算机可以准确的识别出人的位置,并导出多边形路径。

    这里面还涉及一个问题,就是近景识别和远景识别的问题,机器进行识别时只需要识别近景人物,远景人物并不需要进行识别,否则弹幕展示效果会受到很大影响。语义分割可以通过Google的Mask_RCNN来实现。

    客户端实现方案

    客户端的实现方案是通过人像的多边形路径,对原视频抠出人像并导出一个新的视频。在播放的时候实际上是前后两个播放器在播放,弹幕夹在两个播放器中间来实现的。并且前面的人像层需要做边缘虚化,让弹幕的过渡显得自然些,否则会太突兀。

    这种方案的过渡效果会好一些。因为对每一帧视频进行切割的时候,每一帧并不能保证相邻帧切割的边缘相差都不大,也就是相邻近的帧边缘不能保证很好的衔接,这样就容易出现视频连续性的问题。前后两个播放器叠加的方案,两个层的视频内容实际上是衔接很紧密的,把弹幕层去掉你根本看不出来这是两层播放器,所以连续性的问题就不明显了。

    前端实现方案

    前端的实现方案是服务端将多边形路径放在一个svg文件中,并将文件下发给前端,前端通过cssmask‑image遮罩实现的。通过遮罩把人像部分抠出来,人像之外依然是黑色区域,黑色是可显示区域,和iOS的mask属性类似。

    B站是最开始做弹幕防挡的,现在B站已经不局限于真人弹幕防挡了,现在很多番剧中的动漫人物也支持弹幕防挡。可以看下面的视频感受一下。

    B站番剧弹幕防挡视频链接:https://www.bilibili.com/video/BV1Db411C7hJ

    展开全文
  • Dplayer弹幕的获取和提交

    千次阅读 2019-09-28 17:40:37
    vue项目播放M3u8格式的视频,最终选择了...因为Dplayer 文档有默认的弹幕库,你也可以自己搭建弹幕服务器,貌似是node 环境,Dplayer有配置参数,可以提交代码和获取代码,大致是 danmaku: { id: '9E2...

    vue项目中播放M3u8格式的视频,最终选择了Dplayer这款播放器,DPlayer是一个可爱的HTML5 弹幕视频播放器,可以帮助人们轻松地构建视频和弹幕。

    Dplayer帮助文档/官网

    因为Dplayer 文档中有默认的弹幕库,你也可以自己搭建弹幕服务器,貌似是node 环境,Dplayer中有配置参数,可以提交代码和获取代码,大致是

     danmaku: {
            id: '9E2E3368B56CDBB4', //视频的id
            api: 'https://api.prprpr.me/dplayer/', //dplayer中的弹幕接口
            token: 'tokendemo', //这里是做token认证的,
            maximum: 1000, //弹幕获取的数量
            addition: ['https://api.prprpr.me/dplayer/v3/bilibili?aid=4157142'],
            user: 'DIYgod', //弹幕作者
            bottom: '15%',  // 距离头部的距离
            unlimited: true,
        },
    

    因为dplayer的弹幕默认在接口后面添加了 /v3,比如说项目的接口为https://xxx/xxx/xxx?id=1,但是经过dplayer发送的请求就成了https://xxx/xxx/xxx/v3?id=1,这样我们根本就无法获取到数据了。

    解决方案

    • 1 首先,vue项目中不要用 npm install dplayer
    • 2 把dplayer下载到项目中,形成一个dplayer的文件
      项目目录
      • 3 修改dplayer中的DPlayer.min.js
        把 文件内所有带/v3 的代码统统删除,
        ok—大功告成请求
        响应
    展开全文
  • 实时弹幕系统的设计与实现

    万次阅读 2018-12-13 14:44:27
    看新年晚会的时候,发现最大的乐趣就是微信上墙了,但是量大了要等好久才能看见自己发的,为什么不能是弹幕的形式呢? 发现在GitHub上开源了一个JS弹幕模块核心CommentCoreLibrary,慢慢开始学习Node.js的一套。原来...

    前言:原文2014年发布在CNode社区,现在同步一下

    看新年晚会的时候,发现最大的乐趣就是微信上墙了,但是量大了要等好久才能看见自己发的,为什么不能是弹幕的形式呢?
    发现在GitHub上开源了一个JS弹幕模块核心CommentCoreLibrary,慢慢开始学习Node.js的一套。原来是比较做后台开发的,也是第一次做这样的分享,请大家多多指教啦……

    一、Express

    Express是Node.js最流行的一款web框架,小而灵活。Node.js和npm的安装配置可以参考这里

    可以通过npm安装Express(参考),也可以使用Express application generator快速产生一个Express样例(参考)

    对于Express初学者,用Express application generator生成样例更有利于快速上手。因此就以此为例:

    # install Express application generator 
    $ npm install express-generator -g
    
    # create an Express app named danmaku
    $ express danmaku
    
    # install dependencies
    $ cd danmaku
    $ npm install
    
    # run the app on Windows
    $ set DEBUG=danmaku & node .\bin\www
    # or
    $ npm start
    

    关于set DEBUG=danmaku可以见此文
    可以用npm start启动服务器是因为在packege.json中有了这么一段:

      "scripts": {
        "start": "node ./bin/www"
      }
    

    Express的4比之3,把服务器配置和服务器启动做了分离,原来都在app.js里,现在将启动代码放到了www中。
    现在,浏览一遍这个Express样例,对这框架就可以知道个大概了。

    • bin:存放启动项目的脚本文件
    • node_modules:存放所有的项目依赖库
    • public:静态文件(css、js、img等)
    • routes:路由文件(MVC中的C,controller)
    • views:页面文件(jade或ejs模板)
    • package.json:项目依赖配置及开发者信息
    • app.js:应用核心配置文件

    更多参考:

    1. Node.js开发框架Express3.0开发手记–从零开始
    2. Node.js开发框架Express4.x
    3. Express实例

    二、路由

    将实时弹幕系统实际上是分为三个角色:

    • 服务端:监听客户端连接、弹幕事件等并响应。
    • 发射客户端:由用户发射弹幕。以emitCtrl.js作为emit页面的controller。
    • 屏幕客户端:接收弹幕并显示。以indexCtrl.js作为index页面的controller。
      在这里插入图片描述

    添加 routes/indexCtrl.js

    var express = require('express');
    var router = express.Router();
    
    /* GET home page. */
    router.get('/', function(req, res, next) {
      res.render('index',{title:"danmaku"});
    });
    
    module.exports = router;
    
    

    添加 routes/emitCtrl.js

    var express = require('express');
    var router = express.Router();
    
    /* GET emit page. */
    router.get('/', function(req, res, next) {
      res.render('emit');
    });
    
    module.exports = router;
    

    修改 app.js

    var indexCtrl = require('./routes/indexCtrl');
    var emitCtrl = require('./routes/emitCtrl');
    ...
    app.use('/', indexCtrl);
    app.use('/emit', emitCtrl);
    

    启动后可查看到index页面。
    在这里插入图片描述

    在后面还会对emitCtrl.js增加弹幕配置的文件config.json的读取。


    三、屏幕客户端

    1. 静态

    CommentCoreLibrary是GitHub上开源的JS弹幕模块核心,提供从基本骨架到高级弹幕的支持。

    考虑到实际,感觉并不应该引入外部库。如果作为外部库用,需要

    $ npm install comment-core-library --save
    

    使用时(去除public)

    <link rel="stylesheet" href="/node_modules/comment-core-library/build/style.css" />
    <script src="/node_modules/comment-core-library/build/CommentCoreLibrary.js"></script>
    

    另外CommentCoreLibrary模块也有点笨重。

    所以换种方式,将CommentCoreLibrary.js放入public/javascripts,style.css放入public/stylesheets中

    添加views/index.jade

    doctype html
    html
        head
            title= title
            link(rel='stylesheet', href='/stylesheets/style.css')
            link(rel='stylesheet', href='/stylesheets/index.css')
            script(src='/javascripts/CommentCoreLibrary.js')
        body
            #my-player.abp(style='width:100%; height:600px; background:#000;')
                #my-comment-stage.container
            ul#messages
            script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.js')
            script(src='/javascripts/index.js')
    

    添加public/stylesheets/index.css

    * { margin: 0; padding: 0; box-sizing: border-box; }
    #messages { list-style-type: none; margin: 0; padding: 0; }
    #messages li { padding: 5px 10px; }
    #messages li:nth-child(odd) { background: #eee; }
    body {
        margin:0px;
        padding:0px;
        font-family: "Segoe UI", "Microsoft Yahei", sans-serif;
    }
    

    添加public/javascripts/index.js

    window.addEventListener('load', function () {
        // 在窗体载入完毕后再绑定
        var CM = new CommentManager($('#my-comment-stage'));
        CM.init();
        // 先启用弹幕播放(之后可以停止)
        CM.start();
        // 开放 CM 对象到全局这样就可以在 console 终端里操控
        window.CM = CM;
    });
    

    然后在Console里怒射一弹:

    var danmaku = {
        "mode": 1,
        "text": "hello world",
        "stime": 0,
        "size": 25,
        "color": 0xff00ff,
        "dur": 10000
    };
    CM.send(danmaku);
    

    不过这其实根本没用上服务器,也就是静态网页一样的效果。

    在这里插入图片描述

    2. 动态(服务端)

    动态是实现一个真正的“屏幕客户端”,监听等待“显示弹幕”的事件,并实时显示。

    CommentCoreLibrary的Doc中有一段:

    实时弹幕也需要后端服务器的支持。实时弹幕可以采取Polling(定时读取)或者 Push Notify(监听等待)两个主动和被动模式实现。

    WebSocketHTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。既然是双向通信,就意味着服务器端和客户端可以同时发送并响应请求,而不再像HTTP的请求和响应。WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。知乎上关于WebSocket的科普

    Socket.IO是一个开源的WebSocket库,它通过Node.js实现WebSocket服务端,同时也提供客户端JS库。Socket.IO支持以事件为基础的实时双向通讯,它可以工作在任何平台、浏览器或移动设备。
    Socket.IO支持4种协议:WebSocket、htmlfile、xhr-polling、jsonp-polling,它会自动根据浏览器选择适合的通讯方式,从而让开发者可以聚焦到功能的实现而不是平台的兼容性,同时具有不错的稳定性和性能。
    p.s. 实际过程中踩到了phpwebsocket的坑。

    用npm导入Socket.IO

    npm install socket.io --save
    

    修改www(Express4从app.js里把启动分出来了)

    // Create socket.io
    var io = require('socket.io')(server);
    ...
    // Wait for socket event
    io.on('connection', function(socket){
        console.log('a user connected');
        socket.on('disconnect', function(){
            console.log('user disconnected');
        });
        socket.on('danmaku send', function(msg){
            console.log('message: ' + msg);
            io.emit('danmaku show', msg);
        });
    });
    

    修改index.jade

    script(src='http://cdn.bootcss.com/socket.io/1.3.2/socket.io.js')
    script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.js')
    script(src='/javascripts/index.js')
    

    修改index.js

    window.addEventListener('load', function () {
        // 在窗体载入完毕后再绑定
        var CM = new CommentManager($('#my-comment-stage'));
        CM.init();
        // 先启用弹幕播放(之后可以停止)
        CM.start();
        // 开放 CM 对象到全局这样就可以在 console 终端里操控
        window.CM = CM;
    	
        var socket = io();
        socket.on('danmaku show', function (msg) {
            console.log(msg);
            $('#messages').append($('<li>').text(msg));
            var danmaku = JSON.parse(msg);
            CM.send(danmaku);
        });
    });
    

    这样就由服务端监听了“connection”、“disconnect”和“danmaku send”三个事件,特别是在收到“danmaku send”时会发送“danmaku show”事件。而屏幕客户端监听“danmaku show”事件,并把传递来的弹幕显示出来。

    启动后打开index,确实能看到"connection"事件执行的提示。
    在这里插入图片描述

    四、发射客户端

    发射客户端发送“danmaku send”事件及弹幕给服务端。

    除此之外,在CommentCoreLibrary里可以对弹幕属性进行设置,比如文字大小、模式、颜色,将它们的可选值写成配置文件,并设定默认值。

    添加public/jsons/config.json

    {"sizes":[{"size":12,"title":"非常小"},{"size":16,"title":"较小"},{"size":18,"title":"小"},{"size":25,"title":"中"},{"size":36,"title":"大"},{"size":45,"title":"较大"},{"size":64,"title":"非常大"}],
    
    "modes":[{"mode":1,"title":"顶端滚动"},{"mode":2,"title":"底端滚动"},{"mode":5,"title":"顶端渐隐"},{"mode":4,"title":"底端渐隐"},{"mode":6,"title":"逆向滚动"}],
    
    "colors":[{"color":"000000","title":"黑色"},{"color":"C0C0C0","title":"灰色"},{"color":"ffffff","title":"白色"},{"color":"ff0000","title":"红色"},{"color":"00ff00","title":"绿色"},{"color":"0000ff","title":"蓝色"},{"color":"ffff00","title":"黄色"},{"color":"00ffff","title":"墨绿"},{"color":"ff00ff","title":"洋红"}],
    
    "inits":{"size":3,"mode":0,"color":4}}
    

    修改emitCtrl.js,读取配置

    var fs = require('fs');
    ...
    /* GET emit page. */
    router.get('/', function (req, res, next) {
        var config = JSON.parse(fs.readFileSync(__dirname + './../public/jsons/config.json'));
        res.render('emit', { title: 'Emitter', sizes: config.sizes, modes: config.modes, colors: config.colors, inits: config.inits});
    });
    

    添加views/emit.jade

    doctype html
    html
        head
            title= title
            meta(name='viewport', content='width=device-width, initial-scale=1,maximum-scale=1')
            link(rel='stylesheet',href='http://cdn.bootcss.com/jquery-mobile/1.4.3/jquery.mobile.css')
            script(src='http://cdn.bootcss.com/socket.io/1.3.2/socket.io.js')
            script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.min.js')
            script(src='http://cdn.bootcss.com/jquery-mobile/1.4.3/jquery.mobile.js')
        body
            div(data-role='page')
                div(data-role='content')
                    div.ui-grid-b
                        a#size.ui-btn.ui-btn-inline.ui-block-a(href='#popupMenu_font', data-rel='popup', data-transition='pop',data-position-to="window",danmaku-size= sizes[inits.size].size )= sizes[inits.size].title
                        #popupMenu_font(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;')
                            ul(data-role='listview')
                                each val, index in sizes
                                    li
                                        a(data-rel='back',danmaku-size=val.size)= val.title
                        a#mode.ui-btn.ui-btn-inline.ui-block-b(href='#popupMenu_mode', data-rel='popup', data-transition='pop',data-position-to="window",danmaku-mode= modes[inits.mode].mode  )= modes[inits.mode].title
                        #popupMenu_mode(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;')
    
                            ul(data-role='listview')
                                each val, index in modes
                                    li
                                        a(data-rel='back',danmaku-mode=val.mode)= val.title
                        a#color.ui-btn.ui-btn-inline.ui-block-c(href='#popupMenu_color', data-rel='popup', data-transition='pop',data-position-to="window",danmaku-color= colors[inits.color].color  )= colors[inits.color].title
                        #popupMenu_color(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;')
                            .ui-grid-b
                                - var i=0;
                                each val, index in colors
                                    case i++%3
                                        when 0: a.ui-block-a(data-rel="back", style='background-color:#'+val.color+';min-height:60px;line-height:60px;text-align:center',danmaku-color=val.color)= val.title
                                        when 1: a.ui-block-b(data-rel="back", style='background-color:#'+val.color+';min-height:60px;line-height:60px;text-align:center',danmaku-color=val.color)= val.title
                                        when 2: a.ui-block-c(data-rel="back", style='background-color:#'+val.color+';min-height:60px;line-height:60px;text-align:center',danmaku-color=val.color)= val.title 
    
                    textarea#msg(placeholder='来一发弹幕~')
                    button#btnSend 发射
            script(src='/javascripts/emit.js')
    

    添加public/javascripts/emit.js

    var socket = io();
    
    $('#popupMenu_font a').click(function(e){
        $('#size').text($(e.target).text()).attr("danmaku-size",$(e.target).attr("danmaku-size"));
    });
    
    $('#popupMenu_mode a').click(function(e){
        $('#mode').text($(e.target).text()).attr("danmaku-mode",$(e.target).attr("danmaku-mode"));
    });
    
    $('#popupMenu_color a').click(function(e){
        $('#color').text($(e.target).text()).attr("danmaku-color",$(e.target).attr("danmaku-color"));
    });
    
    $('#btnSend').click(function(e){
        e.preventDefault();
        var danmaku = {
            "mode": Number($("#mode").attr("danmaku-mode")),
            "text": $('#msg').val(),
            "stime":0,
            "size": Number($("#size").attr("danmaku-size")),
            "color":parseInt($("#color").attr("danmaku-color"),16),
            "dur":10000
        };
        var msg=JSON.stringify(danmaku);
        console.log(msg);
        socket.emit('danmaku send',msg);
        $('#msg').val("");
    });
    

    最后整个效果就是这样啦~ 源码在此https://github.com/cstackess/danmaku
    在这里插入图片描述

    展开全文
  • 接下来,在body需要放置播放器的位置加入如下代码:最后,关键的部分,配置参数,调用插件。$("#danmup").DanmuPlayer({src: "abc.mp4", //视频源height: "480px", //区域的高度width: "800px", //区域的宽度...
  • 1. 背景在视频网站上,一边看视频一边发弹幕已经是网友的习惯。在B站上有很多种类的视频,也聚集了各种爱好的网友。本项目,就是对B站弹幕数据进行分析。选取分析的对象是B站上点播量过1.4亿的一部剧《Re:零开始的...
  • 用jQuery制作视频弹幕

    2019-01-31 18:50:40
    在播放视频时显示弹幕,自己发布的评论也可显示在弹幕上。
  • 用java爬取斗鱼弹幕

    2020-07-06 19:13:05
    将建立弹幕数据库表 .将信息写入数据库 一、连接websocket 斗鱼弹幕推送是通过websocket进行的消息推送。 Websocket简介:WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术...
  • 点播直播弹幕实现1

    2021-10-17 16:39:29
    点播弹幕实现 后端仓库 简要说明 之前突然觉得弹幕视频还挺有意思的,就想自己也实现点播弹幕和直播弹幕。由于是主学后端的,对于自己手动实现前端的弹幕功能感到困难,于是上网搜索有没有相关实现,首先找到了** ...
  • 今年也有一些新功能选择直接基于 TiDB 做开发,比如店铺标签、直播弹幕和选品服务等。这些新模块的数据量和查询量都还比较小,有待持续观察验证。 TiDB 3.0 GA 后,新的 Titan (https://github.com/tikv/titan) 存储...
  • 抓取斗鱼直播弹幕

    千次阅读 2018-03-05 15:33:53
    原文地址:http://blog.csdn.net/bfboys/article/details/52853041前几...→抓取数据的基本思路就是: 抓包 → 分析请求信息 → 模拟发送请求 → 获得数据知乎回答地址:如何获取斗鱼直播间的弹幕信息? - Brucezz ...
  • 而在他的多部小说,翻拍次数最多的无疑就是《倚天屠龙记》了,而且次数已经高达十四次。最早的是1963香港导演将这个小说以电影形式拍成了上下两集。虽然是第一次,但不得不说导演也是非常的有新意,和金庸多次沟通...
  • 斗鱼:如何打造一个高性能、高可用直播系统架构 蘑菇街直播架构 直播相关知识之一-基本架构 直播中弹幕是一个非常亮眼和重要的功能,相比于秒杀架构,直播弹幕系统也有很多有趣的知识可以挖掘,一起来 YY 下如何...
  • 哔哩哔哩弹幕处理+情感倾向分析

    千次阅读 2021-01-27 20:31:03
    1.首先获取某一视频的弹幕 用此方法获取B站视频的XML弹幕 2.弹幕解析部分 将xml弹幕解析,并写入csv文件 代码部分: import xml.etree.ElementTree as ET import csv import time tree = ET.ElementTree(file='Test...
  • goeasy+jquery+ckplayer实现动态实时视频弹幕

    万次阅读 热门讨论 2017-05-22 12:07:05
    - 由于是实时弹幕,所以需要时时刻刻读取数据库内容,可是大量的ajax请求会影响服务器性能。于是就使用了goeasy第三方实时消息推送框架。 - 弹幕的动态化可以使用jquery的animate动画来实现.弹幕的位置和颜色,随便...
  • 弹幕使用的是阿里云的Aliplayer 参考官网:https://help.aliyun.com/document_detail/125570.html?spm=a2c4g.11186623.6.1099.16b824654fRSp6 ...
  • Dplayer Html5 弹幕视频播放器的实现

    千次阅读 2019-04-26 14:29:34
    弹幕视频,通过 Html5 Dplayer实现。 Dplayer官网:http://dplayer.js.org/#/ Dplayer文档:http://dplayer.js.org/#/zh-Hans/ Dplayer GitHub:https://github.com/MoePlayer/DPlayer 先看效果图 一、...
  • 能够在观看视频的过程发表自己的评论,并且评论可以在你所希望的时间点、位置以滑行或停留的方式出现在视频,所有观看视频的人都可以看见评论,这样一类的评论叫做弹幕,此类网站叫弹幕网站。 弹幕视频系统源自...
  • 给定up主uid和用户uid,爬取用户在该up主所有视频发的所有弹幕 需求拆解 获取up主所有视频 打开b站,随便搜索一个up主,打开所有视频页面,f12看异步请求就一目了然了 接口地址:https://space.bilibili....
  • A代码编辑器,在线模版编辑,仿开发工具编辑器,pdf在线预览,文件转换...带页面、建表sql脚本,处理类,service等完整模块C 集成阿里巴巴数据库连接池druid数据库连接池阿里巴巴的 druid。Druid在监控、可扩展性、...
  • 基准时间为 1970-1-1 08:00:00 弹幕池 0普通池 1字幕池 2特殊池【目前特殊池为高级弹幕专用】 发送者ID 发送者的ID,用于“屏蔽此弹幕的发送者”功能 弹幕ID 弹幕弹幕数据库中rowID 用于“历史弹幕”功能。...
  • 1.1腾讯视频弹幕提取并制作关键词云1:弹幕在哪里1.2腾讯视频弹幕提取并制作关键词云2:scrapy弹幕的获取1.3企鹅弹幕提取并制作关键词云3:自动爬取全集+sql数据库写入1.3 scrapy爬取弹幕爬取弹幕的部分在这一小节...
  • ◆问题发现期初在七月份时,经常发现有几个定时任务报错,查看了下异常原因,大概定位是数据库执行异常◆查找原因1 和 DBA 排查 mycat(公司使用 mycat ) 和 mysql 的...
  • 接下来,在body需要放置播放器的位置加入如下代码:最后,关键的部分,配置参数,调用插件。$("#danmup").DanmuPlayer({src: "abc.mp4", //视频源height: "480px", //区域的高度 width: "800px", //区域的宽度 ...
  • 基于websocket技术的网页弹幕实现

    万次阅读 2017-02-17 17:35:54
    1、打开弹幕从数据库读取历史弹幕; 2、一个客户端发送弹幕,所有的客户端均可以看到。 具体实现: 网页端: index.jsp "java" import="java.util.*" pageEncoding="UTF-8"%> String path =

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,948
精华内容 779
关键字:

从数据库中获读取弹幕

友情链接: case33_57.rar