弹幕的初实现

本周一直在实现弹幕的效果 最后看到弹幕看到这位大佬实现的封装感觉很厉害就去学习了一下他的源码 并且仿照他写了一部分。

弹幕类

这是基本的弹幕类 属性为弹幕id time text fontSize lineHeight color render 并且br是render类的实例
这是对弹幕的渲染

  this.sections.forEach( section => {
              ctx.fillText(section.text, this.left + section.leftOffset, this.top + section.topOffset);
          })
import BarrageRenderer from '../index'
import Utils from '../utils';
//参数对象
export type BaseBarrageOptions = {
    //弹幕id
    id: string;
    //时间
    time: number;
    //文本内容
    text: string;
    fontSize: number;
    lineHeight: number;
    color: string;
    prior?: boolean;
    //自定义render
    customRender?: CustomRender;
    addition?: {
        [key: string]: any
    };
    barrageType: string
}
//弹幕类
export default abstract class BaseBarrage {
    id!: string;
    readonly abstract barrageType: BarrageType;
    time!: number;
    text!: string;
    fontSize!: number;
    lineHeight!: number;
    color!: string;
    prior!: boolean;
    //自定义render
    customRender?: CustomRender;
    addition?: Record<string, any>;
    //渲染器实例
    br: BarrageRenderer;
    top!: number;
    left!: number;
    width!: number;
    height!: number
    sections: Section[]=[];
    protected constructor({
        id,
        time,
        text,
        fontSize,
        lineHeight,
        color,
        prior = false,
        customRender,
        addition,
    }: BaseBarrageOptions, barrageRenderer: BarrageRenderer) {
        this.id = id;
        this.time = time;
        this.text = text;
        this.fontSize = fontSize;
        this.lineHeight = lineHeight;
        this.color = color;
        this.prior = prior;
        this.customRender = customRender;
        this.addition = addition;

        this.br = barrageRenderer;

        this.initBarrage()
    }
    // //渲染指定上下文
    render(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D){
        ctx.beginPath()
        //如果是自定义渲染弹幕的话 触发自定义渲染函数
        if(this.customRender){
            this.customRender.renderFn({
                ctx,
                barrage:this,
                br:this.br,
            })
            return
        }
        if(this.br.devConfig.isRenderBarrageBorder){
            ctx.strokeStyle='#FF0000';
            ctx.strokeRect(this.left, this.top, this.width, this.height);
        }
        //是不是重要的
        if(this.prior){
            if(this.br.renderConfig.priorBorderCustomRender){
                this.br.renderConfig.priorBorderCustomRender({ctx,barrage:this,br:this.br})
            }else{
                ctx.strokeStyle='#89D5FF'
                ctx.strokeRect(this.left,this.top,this.width,this.height)
            }
        }
        this.setCtxFont(ctx)
        ctx.fillStyle=this.color
        this.sections.forEach( section => {
              ctx.fillText(section.text, this.left + section.leftOffset, this.top + section.topOffset);
          })

    }
    initBarrage() {
        const sectionObjects = this.analyseText(this.text);
        // 整个弹幕的宽
        let totalWidth = 0;
        // 整个弹幕的高
        let maxHeight = 0;

        // 计算转换成 sections
        const sections: Section[] = [];
        sectionObjects.forEach(sectionObject => {
            // 设置好文本状态后,进行文本的测量
            this.setCtxFont(this.br.ctx);
            const textWidth = this.br.ctx?.measureText(sectionObject.value).width || 0;
            const textHeight = this.fontSize * this.lineHeight;

            totalWidth += textWidth;
            maxHeight = maxHeight < textHeight ? textHeight : maxHeight;

            // 构建文本片段
            sections.push(new TextSection({
                text: sectionObject.value,
                width: textWidth,
                height: textHeight,
                leftOffset: Utils.Math.sum(sections.map(section => section.width)),
            }));

        })
        this.sections = sections;

        // 设置当前弹幕的宽高,如果自定义中定义了的话,则取自定义中的 width 和 height,因为弹幕实际呈现出来的 width 和 height 是由渲染方式决定的
        this.width = this.customRender?.width ?? totalWidth;
        this.height = this.customRender?.height ?? maxHeight;

        // 遍历计算各个 section 的 topOffset
        this.sections.forEach(item => {
            if (item.sectionType === 'text') {
                item.topOffset = (this.height - this.fontSize) / 2;
            } else {
                item.topOffset = (this.height - item.height) / 2;
            }
        });
    }

    analyseText(barrageText: string): Segment[] {
        const segments: Segment[] = [];

        // 字符串解析器
        while (barrageText) {
            // 尝试获取 ]
            const rightIndex = barrageText.indexOf(']');
            if (rightIndex !== -1) {
                // 能找到 ],尝试获取 rightIndex 前面的 [
                const leftIndex = barrageText.lastIndexOf('[', rightIndex);
                if (leftIndex !== -1) {
                    // [ 能找到
                    if (leftIndex !== 0) {
                        // 如果不等于 0 的话,说明前面是 text
                        segments.push({
                            type: 'text',
                            value: barrageText.slice(0, leftIndex),
                        })
                    }
                    segments.push({
                        type: rightIndex - leftIndex > 1 ? 'image' : 'text',
                        value: barrageText.slice(leftIndex, rightIndex + 1),
                    });
                    barrageText = barrageText.slice(rightIndex + 1);
                } else {
                    // [ 找不到
                    segments.push({
                        type: 'text',
                        value: barrageText.slice(0, rightIndex + 1),
                    })
                    barrageText = barrageText.slice(rightIndex + 1);
                }
            } else {
                // 不能找到 ]
                segments.push({
                    type: 'text',
                    value: barrageText,
                });
                barrageText = '';
            }
        }

        // 相邻为 text 类型的需要进行合并
        const finalSegments: Segment[] = [];
        let currentText = '';
        for (let i = 0; i < segments.length; i++) {
            if (segments[i].type === 'text') {
                currentText += segments[i].value;
            } else {
                if (currentText !== '') {
                    finalSegments.push({ type: 'text', value: currentText });
                    currentText = '';
                }
                finalSegments.push(segments[i]);
            }
        }
        if (currentText !== '') {
            finalSegments.push({ type: 'text', value: currentText });
        }

        return finalSegments;
    }
    setCtxFont(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) {
       ctx.font = `${this.br.renderConfig.fontWeight} ${this.fontSize}px`;
    }

}

//自定义渲染
export type CustomRender = {
    // 弹幕的宽(弹幕实际的宽由具体的渲染操作决定,所以这里由用户自行传入)
    width: number;
    // 弹幕的高(弹幕实际的高由具体的渲染操作决定,所以这里由用户自行传入)
    height: number;
    // 弹幕自定义渲染函数
    renderFn: RenderFn;
}
//自定义渲染函数
export type CustomRenderOptions = {
    ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
    barrage: BaseBarrage;
    br: BarrageRenderer;
    //在lib目录下index.ts
}
export type TextSectionOptions = {
    // 文本片段的内容
    text: string;
    // 这段文本的宽高
    width: number;
    height: number;
    // 当前片段相较于整体弹幕 top 和 left 的偏移量
    topOffset?: number;
    leftOffset: number;
}

export class TextSection {
    readonly sectionType = 'text';
    text: string;
    width: number;
    height: number;
    topOffset!: number;
    leftOffset: number;

    constructor({
        text,
        width,
        height,
        leftOffset,
    }: TextSectionOptions) {
        this.text = text;
        this.width = width;
        this.height = height;
        this.leftOffset = leftOffset;
    }
}
export type Segment = {
    type: 'text' | 'image',
    value: string
}


export type Section = TextSection;
export type RenderFn = (options: CustomRenderOptions) => void;
export type BarrageType = 'scroll' | 'top' | 'bottom' | 'senior';

渲染类

在初始化的时候传入 对初始化render进行处理

 barrageRenderer.value=new BarrageRenderer({
    container:"container",
    video:video.value,
    renderConfig: {
      heightReduce: 60,
      speed: currentSpeed.value,
      renderRegion:1,
      fontWeight: "bold",
      opacity: opacity.value / 100,
      avoidOverlap: avoidFlag.value,
      barrageFilter: (barrage: BaseBarrage) => {
        // 弹幕类型的过滤
        // 弹幕等级过滤
        if (barrage.addition?.grade < shieldGrade.value) return false;
        // 其他情况,不过滤
        return true;
      },
      priorBorderCustomRender: ({ ctx, barrage }) => {
        ctx.save();

        // 设定矩形左上角的偏移量
        const leftOffset = 6;
        const topOffset = 2;
        const { left, top, width, height } = barrage;
        // 设置圆角矩形路径
        ctx.roundRect(
          left - leftOffset,
          top - topOffset,
          width + 2 * leftOffset,
          height + 2 * topOffset,
          10
        );
        // 绘制边框
        ctx.strokeStyle = "#89D5FF";
        ctx.lineWidth = 2;
        ctx.stroke();
        // 绘制背景色
        ctx.fillStyle = "rgba(137, 213, 255, 0.3)";
        ctx.fill();

        ctx.restore();
      },
    },
    devConfig: {
      isRenderFPS: true,
      isRenderBarrageBorder: false,
      isLogKeyData: true,
    },
  })

setDom对Canvas进行初始化 container是父元素 根据子绝父相进行定位
handleHighDprVague进行模糊现象的处理
resize send等方法调用的时候调用布局计算器的实例 也就是 BarrageLayoutCalculate

import type { BarrageOptions } from "./baseBarrage";
import type { RenderFn } from "./baseBarrage/barrageClass"
import type BaseBarrage from "./baseBarrage/barrageClass"
import BarrageLayoutCalculate from "./core"
import Utils from './utils';
//渲染器实例
//弹幕渲染器
export default class BarrageRenderer {
    //容器dom
    container!: HTMLElement | null
    video!: HTMLVideoElement
    canvas!: HTMLCanvasElement
    ctx!: CanvasRenderingContext2D
    //默认渲染配置
    defalutRenderConfig: RenderConfig = {
        heightReduce: 0,
        speed: 200,
        opacity: 1,
        renderRegion: 1,
        avoidOverlap: true,
        minSpace: 10,
        fontWeight:"normal",
        barrageFilter: function (barrage: BaseBarrage): boolean {
            throw new Error("Function not implemented.");
        }
    }
    defalurtDeviceConfig: DevConfig = {
        isRenderFPS: false,
        isRenderBarrageBorder: false,
        isLogKeyData: false,
    }
    devConfig:DevConfig=this.defalurtDeviceConfig
    renderConfig: RenderConfig = this.defalutRenderConfig
    isOpen = true
    animationHandle?: number;
    lastContainerHeight = { width: 0, height: 0 }
    offscreenCanvans!: HTMLCanvasElement
    offerscreenCtx: CanvasRenderingContext2D
    dpr = Utils.Canvas.getDevicePixelRatio();
    fps = ''
    lastFrameTime?: number
    lastScalcTime = 0
    //布局计算器
    barrageLayoutCalculate = new BarrageLayoutCalculate({ barrageRenderer: this, })
    offscreenCanvasCtx: any;
    constructor({
        container,
        video,
        renderConfig,
        devConfig,
        barrages,
    }: RendererOptions) {
        this.video = video;
        // 先设置好渲染配置
        this.setRenderConfigInternal(renderConfig || {}, true);
        // 先设置好开发配置
        this.setDevConfig(devConfig || {});
        // 创建、处理相关 DOM
        this.container =
            typeof container === 'string'
                ? document.getElementById(container)
                : container;
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
        this.offscreenCanvans=document.createElement('canvas')
        this.offerscreenCtx = this.offscreenCanvans.getContext('2d') as CanvasRenderingContext2D;
        this.setDom(this.container,this.canvas,this.ctx)
        //设置弹幕数据
        this.setBarrage(barrages)
    }
    setDevConfig(devConfig: Partial<DevConfig>) {
		Object.assign(this.devConfig, devConfig);
	}
    //弹幕是否被打开
    private setRenderConfigInternal(renderConfig: any, init = false) {
        //获取弹幕渲染配置属性
        const renderCofigKeys = Object.keys(renderConfig)
        const isSpeedChange = renderCofigKeys.includes('speed') && renderConfig.speed !== this.renderConfig.speed;
        const isHeightReduceChange = renderCofigKeys.includes('heightReduce') && renderConfig.heightReduce !== this.renderConfig.heightReduce;
        const isRenderRegionChange = renderCofigKeys.includes('renderRegion') && renderConfig.renderRegion !== this.renderConfig.renderRegion;
        const isAvoidOverlapChange = renderCofigKeys.includes('avoidOverlap') && renderConfig.avoidOverlap !== this.renderConfig.avoidOverlap;
        const isMinSpaceChange = renderCofigKeys.includes('minSpace') && renderConfig.minSpace !== this.renderConfig.minSpace;
        Object.assign(this.renderConfig, renderConfig)
        if (!init && (isSpeedChange || isHeightReduceChange || isRenderRegionChange || isAvoidOverlapChange)) {
			// 高度缩减需要重新处理 DOM
			if (isHeightReduceChange) this.setDom(this.container, this.canvas, this.ctx);
			this.barrageLayoutCalculate.renderConfigChange(
				isSpeedChange,
				isHeightReduceChange,
				isRenderRegionChange,
				isAvoidOverlapChange,
				isMinSpaceChange,
			);
		}
		// 触发一帧的渲染
		if (!this.animationHandle && !init) {
            this.renderShow();
            
        }
    }
    setRenderConfig(renderConfig: any) {
        this.setRenderConfigInternal(renderConfig)
    }
    private setDom(container:HTMLElement|null,canvas:HTMLCanvasElement,ctx:CanvasRenderingContext2D){
        if(!container){console.log('没有容器');}
        if(!ctx){console.log('没有设置画布')}
        if(!container||!ctx) return
        container.style.position='realtive'
        canvas.style.position='absolute'
        canvas.style.left='0px'
        canvas.style.top = '0px';
        canvas.width=container.clientWidth
        canvas.height=container.clientHeight-(this.renderConfig.heightReduce|0)
        container.appendChild(canvas)
        this.handleHighDprVague(canvas, ctx);
		
		// 需要同步处理离屏 canvas
		this.offscreenCanvans.width = container.clientWidth;
		this.offscreenCanvans.height = container.clientHeight - (this.renderConfig.heightReduce ?? 0);
		this.handleHighDprVague(this.offscreenCanvans, this.offerscreenCtx);
    }
    //设置弹幕数据
    setBarrage(barrage?:BarrageOptions[]){
        if(!barrage) return
        //判断弹幕是不是合规的
        barrage=barrage.filter(barrage=>{return this.deleteNoRulues(barrage)==true})
        this.barrageLayoutCalculate.setBarrages(barrage);
        this.lastContainerHeight = {
			width: this.container?.clientWidth || 0,
			height: this.container?.clientHeight || 0,
		}
    }
    private handleHighDprVague(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
		const logicalWidth = canvas.width;
		const logicalHeight = canvas.height;
		canvas.width = logicalWidth * this.dpr;
		canvas.height = logicalHeight * this.dpr;
		canvas.style.width = logicalWidth + 'px';
		canvas.style.height = logicalHeight + 'px';
		
		ctx.scale(this.dpr, this.dpr);
		ctx.textBaseline = 'hanging';
	}
    //判断弹幕是否合法 还有高级 已经error返回的具体形式没写
    
    private deleteNoRulues(barrage:BarrageOptions){
        if(barrage.barrageType=='top'|| barrage.barrageType=='bottom'){
            if(barrage.duration<=0){
                return false
            }
        }
        return true

    }
    //发送新的弹幕
    send(barrage:BarrageOptions){
        const result=this.deleteNoRulues(barrage)
        if(result!==true){
            console.log(result);
            return
        }
        this.barrageLayoutCalculate.send(barrage)
    }
    //重新计算
    resize(){
        this.setDom(this.container,this.canvas,this.ctx)
        const nowSizecontainer={
            width:this.container?.clientWidth||0,
            height:this.container?.clientHeight||0
        }
        const {width,height}=this.lastContainerHeight
        const widthDiff=width!==nowSizecontainer.width
        const heightDiff=height!==nowSizecontainer.height
        if(widthDiff||heightDiff){
            //记录一下
            this.lastContainerHeight=nowSizecontainer
            if(widthDiff && !heightDiff){
                //宽度发生变化
                this.barrageLayoutCalculate.resize('width')
            }else if(heightDiff && !widthDiff){
                //宽度发生变化
                this.barrageLayoutCalculate.resize('height')
            }else{
                this.barrageLayoutCalculate.resize('both')
            }
        }
        this.renderFrame()
    }
    renderFrame(){
        if(!this.animationHandle){this.renderShow()}
    }
    private renderShow(){
        if(!this.isOpen) return
        let renderBarrages=this.barrageLayoutCalculate.getRenderBarrages(this.progress)

        if(this.renderConfig.barrageFilter){
            renderBarrages=renderBarrages.filter(barrage=>this.renderConfig.barrageFilter!(barrage))
        } 
        this.offerscreenCtx.clearRect(0,0,this.offscreenCanvans.width,this.offscreenCanvans.height)
        this.offerscreenCtx.globalAlpha=this.renderConfig.opacity
        renderBarrages.forEach((barrage:BaseBarrage) => {            
            barrage.render(this.offerscreenCtx)
        });
        if(this.devConfig.isRenderFPS) this.renderFPS()
        this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)
        this.offscreenCanvans.width && this.ctx.drawImage(
            this.offscreenCanvans,0,0,this.offscreenCanvans.width,this.offscreenCanvans.height,
            0,0,this.canvas.width/this.dpr,this.canvas.height/this.dpr)
            if(this.animationHandle){
                requestAnimationFrame(()=>this.renderShow())
            }
    }
    get progress(){
        return this.viedoStatus.currentTime;
    }
    get viedoStatus(){
        return{
            // currenTime
            currentTime:this.video.currentTime*1000,
            playing:!this.video.paused
        }
    }
    private renderFPS(){
        const now=Date.now();
        if(this.lastFrameTime && now -this.lastScalcTime>200){
            this.fps = `${Math.floor(1000 / (now - this.lastFrameTime))}FPS`;
			this.lastScalcTime = now;
        }
        this.lastFrameTime = now;

		if (this.fps) {
			this.offerscreenCtx.font = 'bold 32px Microsoft YaHei';
			this.offerscreenCtx.fillStyle = 'blue';
			this.offerscreenCtx.fillText(this.fps, 20, 30);
		}
    }
    private createAnimation(){
        if(!this.animationHandle && this.isOpen){
            this.animationHandle=requestAnimationFrame(()=>
            {this.renderShow()})
        }
    }
    play(){        
        this.createAnimation()
    }
    pause() {
		this.animationHandle && cancelAnimationFrame(this.animationHandle);
		this.animationHandle = undefined;
	}
    get canvaSize(){
        return {
            width:this.canvas.width/this.dpr,
            height:this.canvas.height/this.dpr
        }
    }
    getRenderBarrages(time: number): BaseBarrage[] {
        // 获取需要渲染的滚动弹幕
        const renderScrollBarrages = this.getRenderBarrages(time);
        // 获取需要渲染的固定弹幕
        const renderFixedBarrages = this.getRenderBarrages(time);
        // 获取需要渲染的高级弹幕
        const renderSeniorBarrages = this.getRenderBarrages(time);
        // 整合排序
        return [
          ...renderScrollBarrages,
          ...renderFixedBarrages,
          ...renderSeniorBarrages,
        ].sort((a, b) => {
          if (a.prior !== b.prior) {
            // 如果 a 的 prior 为 true,则返回 1,否则返回 -1
            // 这意味着 true 的值会在最后面
            return a.prior ? 1 : -1;
          } else {
            // 如果 prior 属性相同,则按照 time 属性排序
            return a.time - b.time;
          }
        });
      }


}
export type RenderConfig = {
    barrageFilter: (barrage: BaseBarrage) => boolean
    priorBorderCustomRender?: RenderFn;
    heightReduce: number;
    speed: number;
    renderRegion: number;
    minSpace: number;
    avoidOverlap: boolean;
    opacity: number;
    fontWeight:string
}
export type RendererOptions = {
    // 容器 DOM
    container: string | HTMLElement;
    // video 元素(获取 video 元素,只是为了获取播放状态,不会对 video 执行其他操作)
    video: HTMLVideoElement;
    // 弹幕数据
    barrages?: BarrageOptions[];
    // // 渲染相关配置
    renderConfig?: Partial<RenderConfig>;
    // // 开发相关配置
    devConfig?: Partial<DevConfig>,
}
//开发相关配置
export type DevConfig = {
    // 是否渲染 fps
    isRenderFPS: boolean;
    // 是否渲染弹幕边框
    isRenderBarrageBorder: boolean;
    // 是否打印关键数据
    isLogKeyData: boolean;
}
export {
    BarrageRenderer,
};

布局计算器类

setBarrages对于类型不同 返回不同的实体类

import type BarrageRenderer from "..";
import type { BarrageOptions } from "../baseBarrage";
import type BaseBarrage from "../baseBarrage/barrageClass";
import type { BaseBarrageOptions } from "../baseBarrage/barrageClass";
import FixedBarrage from "../baseBarrage/fixedBarrage";
import SrollBarrage from "../baseBarrage/srollBarrage";
import FixedBarrageLayout from "./fixed-barrage-layout";
import Utils from '../utils';
import VirtualBarrage from "./virtualScroll";
export default class BarrageLayoutCalculate {
    br!: BarrageRenderer
    allBarrage: BaseBarrage[] = []
    fixedBarrage: FixedBarrage[] = []
    scrollBarrage: SrollBarrage[] = []
    senioBarrage!: []
    fixedLayout!: FixedBarrageLayout
    virtualTrackAlgorithm: VirtualBarrage;
    constructor({ barrageRenderer }: BarrageLayoutCalculateOptions) {
        this.br = barrageRenderer
        this.fixedLayout = new FixedBarrageLayout(this.br)
        this.virtualTrackAlgorithm = new VirtualBarrage(this.br);
    }
    
    setBarrages(barrageOptions: BarrageOptions[]) {
        let barrageInstance = barrageOptions.map((barrageOption) => {
            switch (barrageOption.barrageType) {
                case 'top':
                case 'bottom':
                    return new FixedBarrage(barrageOption, this.br);
                case 'scroll':
                    return new SrollBarrage(barrageOption, this.br);
                // case 'senior':
                //高级弹幕 还没写
            }
        })
        barrageInstance = barrageInstance.sort((a, b) => a!.time - b!.time)
        this.allBarrage = barrageInstance
        this.fixedBarrage = barrageInstance.filter(instance =>
            ['top', 'bottom'].includes(instance.barrageType)) as FixedBarrage[];
        this.scrollBarrage = barrageInstance.filter(instance => instance.barrageType === 'scroll') as SrollBarrage[];
        this.virtualTrackAlgorithm.layoutScrollBarrages(this.scrollBarrage);
        
        
    }
    resize(type: 'width' | 'height' | 'both') {
        // 高级弹幕的 start 和 end 定位全部重新计算
        if (type === 'width') {
          this.handleWidthChange();
        } else if (type === 'height') {
          this.handleHeightChange();
        } else {
          this.handleWidthChange();
          this.handleHeightChange();
        }
      }
      private handleWidthChange() {
        // 固定弹幕 -- 重新计算 left
        this.fixedBarrage.forEach(barrage => barrage.calFixedBarrageLeft());
        // 滚动弹幕 -- 重新计算 originalLeft
        this.scrollBarrage.forEach(barrage => barrage.cancalSrcoll());
      }
      private handleHeightChange () {
        // 固定弹幕 -- 清空现有的即可
        this.fixedLayout.clearStoredBarrage();
        // 滚动弹幕 -- 布局完全重新计算
        this.virtualTrackAlgorithm.heightChangeReLayoutCalc(this.scrollBarrage);
      }
    send(barrage: BarrageOptions) {
        // 根据弹幕类型进行不同的处理
        if (barrage.barrageType === 'scroll') {
            // 滚动弹幕
            const scrollBarrage = new SrollBarrage(barrage, this.br);
            Utils.Algorithm.insertBarrageByTime(this.scrollBarrage, scrollBarrage);
            this.virtualTrackAlgorithm.send(scrollBarrage);
        } else if (barrage.barrageType === 'top' || barrage.barrageType === 'bottom') {
            // 固定弹幕
            const fixedBarrage = new FixedBarrage(barrage, this.br);
            Utils.Algorithm.insertBarrageByTime(this.fixedBarrage, fixedBarrage);
            this.fixedLayout.send(fixedBarrage);
        // } else if (barrage.barrageType === 'senior') {
        //     // 高级弹幕
        //     const seniorBarrage = new SeniorBarrage(barrage, this.br);
        //     Utils.Algorithm.insertBarrageByTime(this.seniorBarrageInstances, seniorBarrage);
        // }
    }
}
    getRenderBarrages(time: number): BaseBarrage[] {
        // 获取需要渲染的滚动弹幕
        const renderScrollBarrages = this.getRenderScrollBarrages(time);
        
        // 获取需要渲染的固定弹幕
        const renderFixedBarrages = this.getRenderFixedBarrages(time);
        // 获取需要渲染的高级弹幕
       console.log(renderFixedBarrages);
       
        // 整合排序
        return [
          ...renderScrollBarrages,
          ...renderFixedBarrages,
         
        ].sort((a, b) => {
          if (a.prior !== b.prior) {
            // 如果 a 的 prior 为 true,则返回 1,否则返回 -1
            // 这意味着 true 的值会在最后面
            return a.prior ? 1 : -1;
          } else {
            // 如果 prior 属性相同,则按照 time 属性排序
            return a.time - b.time;
          }
        });
      }
      renderConfigChange(
        isSpeedChange: boolean,
        isHeightReduceChange: boolean,
        isRenderRegionChange: boolean,
        isAvoidOverlapChange: boolean,
        isMinSpaceChange: boolean,
      ) {
        // 重新计算 originalLeft 事项
        if (isSpeedChange) {
          this.scrollBarrage.forEach(barrage => barrage.cancalSrcoll());
        }
        // 清空固定弹幕的 store 事项
        if (isHeightReduceChange) {
          this.fixedLayout.clearStoredBarrage();
        }
        // 重置轨道数据 事项
        if (isHeightReduceChange || isRenderRegionChange) {
          this.virtualTrackAlgorithm.resetTracks();
        }
        // 根据 avoidOverlap 进行重新布局 事项
        if (
          (isSpeedChange && this.br.renderConfig.avoidOverlap) ||
          isHeightReduceChange ||
          isRenderRegionChange ||
          isAvoidOverlapChange ||
          (isMinSpaceChange && this.br.renderConfig.avoidOverlap)
        ) {
          this.virtualTrackAlgorithm.layoutScrollBarrages(this.scrollBarrage);
        }
      }
      
      getRenderScrollBarrages(time: number): SrollBarrage[] {
        // 弹幕整体向左移动的总距离,时间 * 速度
        const translateX = (time / 1000) * this.br.renderConfig.speed;
        console.log(this.scrollBarrage,'4888');
        
        const renderScrollBarrages = this.scrollBarrage.filter(barrage => barrage.show && barrage.top !== undefined).filter(barrage =>
          barrage.originalRright - translateX >= 0 &&
          barrage.originalLeft - translateX < this.br.canvaSize.width
        );
    
        renderScrollBarrages.forEach(barrage => {
          barrage.left = barrage.originalLeft - translateX;
        })
        return renderScrollBarrages;
      }
      getRenderFixedBarrages(time: number): FixedBarrage[] {
        return this.fixedLayout.getRenderFixedBarrages(this.fixedBarrage, time);
      }
}
export type BarrageLayoutCalculateOptions = {
    barrageRenderer: BarrageRenderer
}

总结

这个实现效果还是有点问题 但是看别人的代码学习确实很有效果 学习到了怎么在ts定义变量并在vue使用 学习到了反射思想等等 还是很有收获的

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android 应用中实现弹幕,可以考虑使用 SurfaceView 和 Canvas 组合来实现。 具体步骤如下: 1. 创建一个 SurfaceView,并在其上绘制弹幕。可以通过 SurfaceHolder 获取 Canvas 对象,然后在 Canvas 上绘制文本。 2. 创建一个弹幕控制器,用于控制弹幕的生成和绘制。弹幕控制器需要维护一个弹幕列表,每隔一定时间生成新的弹幕并添加到列表中。 3. 在 SurfaceView 的回调方法中,绘制弹幕列表中的所有弹幕,并更新弹幕的位置。 4. 可以根据需要添加一些特效,比如透明度渐变、移动速度变化等。 示例代码如下: ```java public class DanmakuSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder surfaceHolder; private DanmakuController danmakuController; public DanmakuSurfaceView(Context context) { super(context); init(); } public DanmakuSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DanmakuSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { surfaceHolder = getHolder(); surfaceHolder.addCallback(this); danmakuController = new DanmakuController(); } @Override public void surfaceCreated(SurfaceHolder holder) { danmakuController.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { danmakuController.stop(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); List<Danmaku> danmakus = danmakuController.getDanmakus(); for (Danmaku danmaku : danmakus) { Paint paint = new Paint(); paint.setTextSize(danmaku.getTextSize()); paint.setColor(danmaku.getTextColor()); paint.setAlpha(danmaku.getAlpha()); canvas.drawText(danmaku.getText(), danmaku.getX(), danmaku.getY(), paint); } } } ``` 弹幕控制器代码示例: ```java public class DanmakuController implements Runnable { private boolean isRunning; private List<Danmaku> danmakus; public DanmakuController() { danmakus = new ArrayList<>(); } public void start() { isRunning = true; new Thread(this).start(); } public void stop() { isRunning = false; } public List<Danmaku> getDanmakus() { return danmakus; } @Override public void run() { while (isRunning) { // 生成新的弹幕并添加到列表中 Danmaku danmaku = new Danmaku(); danmakus.add(danmaku); // 更新弹幕位置 for (Danmaku d : danmakus) { d.move(); } // 每隔一定时间刷新界面 try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 弹幕类代码示例: ```java public class Danmaku { private static final int SPEED = 10; // 弹幕速度 private static final int MAX_ALPHA = 255; // 最大透明度 private String text; // 弹幕文本 private int textColor; // 弹幕颜色 private int textSize; // 弹幕字体大小 private int x; // 弹幕横坐标 private int y; // 弹幕纵坐标 private int alpha; // 弹幕透明度 public Danmaku() { text = "Hello, world!"; textColor = Color.WHITE; textSize = 36; x = 0; y = 0; alpha = MAX_ALPHA; } public String getText() { return text; } public void setText(String text) { this.text = text; } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; } public int getTextSize() { return textSize; } public void setTextSize(int textSize) { this.textSize = textSize; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getAlpha() { return alpha; } public void setAlpha(int alpha) { this.alpha = alpha; } public void move() { x += SPEED; alpha -= 5; if (alpha < 0) { alpha = 0; } } } ``` 这只是一个简单的示例,实际开发中需要根据具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值