├── src ├── i18n │ ├── README.md │ ├── zh-cn.ts │ └── index.ts ├── Controller │ ├── README.md │ └── Controller.ts ├── Factory │ ├── README.md │ ├── DanmakuParser │ │ ├── codeDanmaku │ │ │ ├── JsonPreprocessInterface.ts │ │ │ ├── JsonPostprocessInterface.ts │ │ │ ├── DanmakuDefaultAttr.ts │ │ │ ├── DanmakuDelay.ts │ │ │ ├── DanmakuPosititon.ts │ │ │ ├── DanmakuValidate.ts │ │ │ ├── PostprocessPipe.ts │ │ │ ├── DanmakuThen.ts │ │ │ ├── PreprocessPipe.ts │ │ │ ├── DanmakuExtends.ts │ │ │ └── CodeDanmakuParser.ts │ │ ├── DanmakuParserInterface.ts │ │ ├── DanmakuFactory.ts │ │ ├── DanmakuParserFactory.ts │ │ └── mode7Danmaku │ │ │ └── Mode7DanmakuParser.ts │ ├── TimeLineFactory.ts │ ├── RendererFactory.ts │ └── AnimationFactory.ts ├── util │ ├── README.md │ ├── HTMLEnode.ts │ ├── deepMerge.ts │ └── UnitTools.ts ├── Context │ ├── README.md │ └── Context.ts ├── TimeLine │ ├── README.md │ ├── TimeLineDanmaku.ts │ ├── TimeLineInterface.ts │ └── AdvancedLine.ts ├── core │ ├── Style │ │ ├── Unit │ │ │ ├── UnitInterface.ts │ │ │ ├── PxSize.ts │ │ │ ├── Border.ts │ │ │ ├── Shadow.ts │ │ │ └── Color.ts │ │ ├── DanmakuStyle.ts │ │ ├── PositionInterface.ts │ │ ├── SizeInterface.ts │ │ ├── CanvasStyle.ts │ │ ├── BoxStyleInterface.ts │ │ └── FontInterface.ts │ ├── README.md │ ├── Stage │ │ ├── Mode7Stage.ts │ │ ├── CodeDanmakuStage.ts │ │ ├── StageInterface.ts │ │ └── BaseStage.ts │ ├── Animation │ │ ├── TransformsAnimations │ │ │ ├── RotationXAnimation.ts │ │ │ ├── RotationYAnimation.ts │ │ │ ├── RotationZAnimation.ts │ │ │ ├── RotationAnimation.ts │ │ │ ├── StaticAnimation.ts │ │ │ ├── ScaleAnimations.ts │ │ │ └── TranslateAnimation.ts │ │ ├── Base │ │ │ ├── AnimationInterface.ts │ │ │ ├── OriginAnimations.ts │ │ │ ├── Cubic.ts │ │ │ ├── Matrix.ts │ │ │ └── CubicAnimations.ts │ │ ├── styleAnimations │ │ │ └── OpacityAnimations.ts │ │ └── CompositeAnimation │ │ │ ├── RepeatAnimations.ts │ │ │ ├── GroupAnimations.ts │ │ │ └── ListAnimations.ts │ ├── Renderer │ │ ├── RendererInterface.ts │ │ ├── BaseRenderer.ts │ │ └── CSS3Renderer │ │ │ ├── CSS3Renderer.ts │ │ │ └── CssDanmakuObj.ts │ └── Danmaku │ │ ├── DanmakuItemInterface.ts │ │ └── BaseDanmaku.ts ├── InitConfigInterface.ts ├── Event │ ├── DanmakuEventType.ts │ └── DanmakuEvent.ts └── index.ts ├── .vscode └── settings.json ├── .gitignore ├── test ├── test.css ├── test.json └── index.html ├── css └── base.css ├── style.css ├── tsconfig.json ├── package.json ├── webpack.config.js ├── tslint.json ├── README.md └── lib └── mfunsAdvancedDanmaku.js /src/i18n/README.md: -------------------------------------------------------------------------------- 1 | 多语言 -------------------------------------------------------------------------------- /src/Controller/README.md: -------------------------------------------------------------------------------- 1 | 控制器 -------------------------------------------------------------------------------- /src/Factory/README.md: -------------------------------------------------------------------------------- 1 | 弹幕解析工厂 -------------------------------------------------------------------------------- /src/util/README.md: -------------------------------------------------------------------------------- 1 | 工具相关文件 -------------------------------------------------------------------------------- /src/Context/README.md: -------------------------------------------------------------------------------- 1 | 上下文(目前用途不大 -------------------------------------------------------------------------------- /src/TimeLine/README.md: -------------------------------------------------------------------------------- 1 | 时间轴管理模块 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | src/player/static 4 | src/player/index.html -------------------------------------------------------------------------------- /src/core/Style/Unit/UnitInterface.ts: -------------------------------------------------------------------------------- 1 | export interface UnitInterface{ 2 | string():string 3 | } -------------------------------------------------------------------------------- /src/core/Style/DanmakuStyle.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 弹幕样式集合 3 | */ 4 | export interface DanmakuStyle{ 5 | [idx: string]: any; 6 | } -------------------------------------------------------------------------------- /src/Context/Context.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局上下文 3 | */ 4 | export class Context { 5 | /** 6 | * 当前语言 7 | */ 8 | public static language: string = "zh-cn"; 9 | public static debug = true; 10 | } 11 | -------------------------------------------------------------------------------- /src/core/Style/PositionInterface.ts: -------------------------------------------------------------------------------- 1 | import { PxSize } from "./Unit/PxSize"; 2 | 3 | 4 | 5 | /** 6 | * 位置接口 7 | * 使用屏幕坐标系 8 | */ 9 | export interface PositionInterface{ 10 | x:PxSize, 11 | y:PxSize 12 | } -------------------------------------------------------------------------------- /src/InitConfigInterface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 初始化配置接口 3 | */ 4 | 5 | export interface InitConfigInterface { 6 | /** 7 | * 渲染的容器 8 | */ 9 | containers?: HTMLElement; 10 | codeDanmaku?: () => string; 11 | mode7Danmaku?: () => string; 12 | } 13 | -------------------------------------------------------------------------------- /src/core/README.md: -------------------------------------------------------------------------------- 1 | 核心目录 2 | 3 | Animations 动画类型 4 | - Base 动画基础类 5 | - CompositeAnimation 复合动画类型 6 | - styleAnimations 样式动画类型 7 | - TransformsAnimations 2d/3d变换动画类型 8 | 9 | Danmaku 弹幕类型 10 | 11 | Renderer 渲染器 12 | 13 | Stage 舞台 14 | 15 | Style 样式,单位大小接口 -------------------------------------------------------------------------------- /src/core/Style/SizeInterface.ts: -------------------------------------------------------------------------------- 1 | import { PxSize } from "./Unit/PxSize"; 2 | 3 | /** 4 | * 尺寸接口 5 | */ 6 | export interface SizeInterface{ 7 | /** 8 | * 宽度 9 | */ 10 | width:PxSize, 11 | /** 12 | * 高度 13 | */ 14 | height:PxSize 15 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/JsonPreprocessInterface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * json弹幕预处理接口 3 | */ 4 | export interface JsonPreprocessInterface { 5 | /** 6 | * 处理弹幕 当返回false说明处理失败 7 | * @param josn json弹幕 8 | */ 9 | process(json: any, index: number): any | false; 10 | } 11 | -------------------------------------------------------------------------------- /src/i18n/zh-cn.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "Containers is null" : "容器为空", 3 | "Start mount stage" : "开始挂载舞台", 4 | "Renderer type is null": "没有找到合适的渲染器", 5 | "matrix3d param less than 16":"matrix3d 参数小于16个", 6 | "Unknown danmaku format":"未知的弹幕格式", 7 | "Danmaku parser fail":"高级弹幕解析失败,json格式可能存在问题" 8 | } -------------------------------------------------------------------------------- /src/core/Style/CanvasStyle.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "./Unit/Color"; 2 | import { PositionInterface } from "./PositionInterface"; 3 | import { SizeInterface } from "./SizeInterface"; 4 | /** 5 | * 画布样式集合 6 | */ 7 | export interface CanvasStyle{ 8 | size:SizeInterface, 9 | color:Color, 10 | position:PositionInterface 11 | } -------------------------------------------------------------------------------- /src/core/Style/Unit/PxSize.ts: -------------------------------------------------------------------------------- 1 | import { UnitInterface } from "./UnitInterface"; 2 | 3 | export class PxSize implements UnitInterface{ 4 | public length:number 5 | constructor(length:number){ 6 | this.length = length; 7 | } 8 | public string(): string { 9 | return this.length +"px"; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/DanmakuParserInterface.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../../core/Danmaku/DanmakuItemInterface"; 2 | 3 | /** 4 | * 弹幕解析器,通常传入字符串 5 | */ 6 | export interface DanmakuParserInterface{ 7 | /** 8 | * 解析 9 | * @param content 弹幕内容 10 | */ 11 | parser(content:string):DanmakuItemInterface[] 12 | } -------------------------------------------------------------------------------- /src/TimeLine/TimeLineDanmaku.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../core/Danmaku/DanmakuItemInterface"; 2 | 3 | export interface TimeLineDanmaku{ 4 | /** 5 | * 弹幕本体 6 | */ 7 | danmaku:DanmakuItemInterface; 8 | /** 9 | * 开始时间 10 | */ 11 | start:number, 12 | /** 13 | * 结束时间 14 | */ 15 | end:number, 16 | } -------------------------------------------------------------------------------- /src/core/Stage/Mode7Stage.ts: -------------------------------------------------------------------------------- 1 | import { BaseStage } from "./BaseStage"; 2 | 3 | /** 4 | * mode7 弹幕舞台 5 | */ 6 | export class Mode7Stage extends BaseStage { 7 | public rendererType(): string { 8 | return "css3"; 9 | } 10 | public attachedType(): string { 11 | return "mode7"; 12 | } 13 | public timeLineType(): string { 14 | return "advanced"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/core/Stage/CodeDanmakuStage.ts: -------------------------------------------------------------------------------- 1 | import { BaseStage } from "./BaseStage"; 2 | 3 | /** 4 | * 代码弹幕舞台 5 | */ 6 | export class CodeDanmakuStage extends BaseStage { 7 | public timeLineType(): string { 8 | return "advanced"; 9 | } 10 | public rendererType(): string { 11 | return "css3"; 12 | } 13 | public attachedType(): string { 14 | return "code"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | #body ,#body2{ 2 | 3 | width: 100%; 4 | height: 100%; 5 | } 6 | #editor { 7 | margin: 0; 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | right: 0; 12 | left:0; 13 | } 14 | .box{ 15 | width:100%; 16 | height:600px; 17 | position: relative; 18 | border:1px solid #888; 19 | } 20 | -------------------------------------------------------------------------------- /src/core/Style/BoxStyleInterface.ts: -------------------------------------------------------------------------------- 1 | import { Border } from "./Unit/Border"; 2 | import { Color } from "./Unit/Color"; 3 | import { Shadow } from "./Unit/Shadow"; 4 | 5 | export interface BoxStyleInterface{ 6 | // 边框 7 | borderTop?:Border 8 | borderBottom?:Border 9 | borderLeft?:Border 10 | borderRight?:Border 11 | // 阴影 12 | boxShadow?:Shadow 13 | // 背景 14 | backgroundColor?:Color 15 | 16 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/JsonPostprocessInterface.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../../../core/Danmaku/DanmakuItemInterface"; 2 | 3 | /** 4 | * Json弹幕 后处理接口 5 | */ 6 | export interface JsonPostprocessInterface { 7 | /** 8 | * 处理弹幕 9 | * @param danmaku 弹幕对象 10 | * @param params json参数 11 | */ 12 | process( 13 | danmaku: DanmakuItemInterface, 14 | params: any 15 | ): DanmakuItemInterface | false; 16 | } 17 | -------------------------------------------------------------------------------- /src/util/HTMLEnode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * html转义实体 3 | * @param str 4 | * @returns 5 | */ 6 | export function HTMLEnCode(str: string) { 7 | let s = ""; 8 | if (str.length === 0) { 9 | return ""; 10 | } 11 | s = str.replace(/&/g, ">"); 12 | s = s.replace(/ /g, ">"); 14 | s = s.replace(/ /g, " "); 15 | s = s.replace(/\'/g, "'"); 16 | s = s.replace(/\"/g, """); 17 | return s; 18 | } 19 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/DanmakuDefaultAttr.ts: -------------------------------------------------------------------------------- 1 | import { JsonPreprocessInterface } from "./JsonPreprocessInterface"; 2 | /** 3 | * 弹幕默认属性设置 4 | */ 5 | export class DanmakuDefaultAttr implements JsonPreprocessInterface { 6 | public process(json: any, _index: number) { 7 | // 添加默认的动画 8 | if (!json?.animations || json?.animations === []) { 9 | json.animations = [{ type: "static" }]; 10 | } 11 | return json; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "111111", 4 | "size": 250, 5 | "color": "#0000ff", 6 | "weight": 900, 7 | "shadow": true, 8 | "stroke": 10, 9 | "strokeColor": "#550000", 10 | "font": "宋体", 11 | "zIndex": 3, 12 | "linear": 0, 13 | "animations": [ 14 | { 15 | "x": 100, 16 | "y": 100 17 | }, 18 | { 19 | "x": 200, 20 | "y": 800, 21 | "scale": 0.8 22 | } 23 | ] 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /src/Factory/TimeLineFactory.ts: -------------------------------------------------------------------------------- 1 | import { AdvancedLine } from "../TimeLine/AdvancedLine"; 2 | import { TimeLineInterface } from "../TimeLine/TimeLineInterface"; 3 | 4 | export class TimeLineFactory{ 5 | public static list = { 6 | "default":AdvancedLine, 7 | "advanced":AdvancedLine 8 | } 9 | public static getTimeLine(type:string):TimeLineInterface{ 10 | if(!this.list[type]){ 11 | type = "default" 12 | } 13 | return new this.list[type]() 14 | } 15 | } -------------------------------------------------------------------------------- /src/util/deepMerge.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 对象深度合并 3 | * @param obj1 4 | * @param obj2 5 | * @returns 6 | */ 7 | export function deepMerge(obj1, obj2) { 8 | let key; 9 | // tslint:disable-next-line: forin 10 | for (key in obj2) { 11 | obj1[key] = 12 | obj1[key] && 13 | obj1[key].toString() === "[object Object]" && 14 | obj2[key] && 15 | obj2[key].toString() === "[object Object]" 16 | ? deepMerge(obj1[key], obj2[key]) 17 | : (obj1[key] = obj2[key]); 18 | } 19 | return obj1; 20 | } 21 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/DanmakuFactory.ts: -------------------------------------------------------------------------------- 1 | import { BaseDanmaku } from "../../core/Danmaku/BaseDanmaku"; 2 | import { DanmakuItemInterface } from "../../core/Danmaku/DanmakuItemInterface"; 3 | 4 | /** 5 | * 弹幕解析器工厂 6 | */ 7 | export class DanmakuFactory{ 8 | public static list = { 9 | "text":BaseDanmaku 10 | } 11 | public static getDanmakuInstance(type:string):DanmakuItemInterface{ 12 | if(!this.list[type]){ 13 | type = "text" 14 | } 15 | return new this.list[type]() 16 | } 17 | } -------------------------------------------------------------------------------- /src/Event/DanmakuEventType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 弹幕事件类型枚举 3 | */ 4 | export enum DanmakuEventType { 5 | /** 6 | * 弹幕字符串无效 7 | */ 8 | DANMAKU_JSON_INVALID = "DANMAKU_JSON_INVALID", 9 | /** 10 | * 单条弹幕格式错误 11 | */ 12 | DANMAKU_FORMAT_ERROR = "DANMAKU_FORMAT_ERROR", 13 | 14 | /** 15 | * 弹幕开始加载 16 | */ 17 | DANMAKU_LOAD_START = "DANMAKU_LOAD_START", 18 | 19 | /** 20 | * 弹幕加载完成 21 | */ 22 | DANMAKU_LOAD_DONE = "DANMAKU_LOAD_DONE", 23 | 24 | /** 25 | * 弹幕舞台重置 26 | */ 27 | DANMAKU_STAGE_RESET = "DANMAKU_STAGE_RESET" 28 | } 29 | -------------------------------------------------------------------------------- /src/core/Style/Unit/Border.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "./Color"; 2 | import { UnitInterface } from "./UnitInterface"; 3 | 4 | /** 5 | * 边框 6 | */ 7 | export class Border implements UnitInterface{ 8 | constructor(width:number,style:string,color:Color){ 9 | this.width = width 10 | this.color = color 11 | this.style = style 12 | } 13 | public width:number 14 | public style:string 15 | public color:Color 16 | public string(): string { 17 | return "" + this.width + "px " + this.style + " " + this.color.string 18 | } 19 | } -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/RotationXAnimation.ts: -------------------------------------------------------------------------------- 1 | import { RotationAnimation } from "./RotationAnimation"; 2 | 3 | /** 4 | * X轴旋转 5 | */ 6 | export class RotationXAnimation extends RotationAnimation { 7 | public getMatrixForOrigin(progress: number): false | number[] { 8 | let angle = this.getProgressValue( 9 | this.angle.start, 10 | this.angle.end, 11 | progress 12 | ); 13 | let sin = Math.sin(this.A2R(angle)); 14 | let cos = Math.cos(this.A2R(angle)); 15 | 16 | return [1, 0, 0, 0, 0, cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/RotationYAnimation.ts: -------------------------------------------------------------------------------- 1 | import { RotationAnimation } from "./RotationAnimation"; 2 | /** 3 | * y轴旋转 4 | */ 5 | export class RotationYAnimation extends RotationAnimation { 6 | public getMatrixForOrigin(progress: number): false | number[] { 7 | let angle = this.getProgressValue(this.angle.start,this.angle.end,progress) 8 | let sin = Math.sin(this.A2R(angle)); 9 | let cos = Math.cos(this.A2R(angle)) 10 | return [ 11 | cos, 0, sin, 0, 12 | 0, 1, 0, 0, 13 | -sin, 0, cos, 0, 14 | 0, 0, 0, 1 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/RotationZAnimation.ts: -------------------------------------------------------------------------------- 1 | import { RotationAnimation } from "./RotationAnimation"; 2 | 3 | /** 4 | * z轴旋转 5 | */ 6 | export class RotationZAnimation extends RotationAnimation { 7 | public getMatrixForOrigin(progress: number): false | number[] { 8 | let angle = this.getProgressValue(this.angle.start,this.angle.end,progress) 9 | let sin = Math.sin(this.A2R(angle)); 10 | let cos = Math.cos(this.A2R(angle)) 11 | return [ 12 | cos,sin, 0, 0, 13 | -sin, cos, 0, 0, 14 | 0, 0,1, 0, 15 | 0, 0, 0, 1 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/DanmakuDelay.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../../../core/Danmaku/DanmakuItemInterface"; 2 | import { JsonPostprocessInterface } from "./JsonPostprocessInterface"; 3 | 4 | export class DanmakuDelay implements JsonPostprocessInterface { 5 | public process( 6 | danmaku: DanmakuItemInterface, 7 | params: any 8 | ): false | DanmakuItemInterface { 9 | if (params?.delay) { 10 | let delay = parseInt(params.delay, 10); 11 | let start = danmaku.startTime() + delay; 12 | danmaku.setParams({ 13 | start, 14 | }); 15 | } 16 | return danmaku; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/TimeLine/TimeLineInterface.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../core/Danmaku/DanmakuItemInterface"; 2 | import { TimeLineDanmaku } from "./TimeLineDanmaku"; 3 | 4 | export interface TimeLineInterface { 5 | /** 6 | * 获取弹幕列表 7 | * @param time 当前播放时间 8 | */ 9 | getDanmakuList(time: number): { 10 | skip: boolean; 11 | DanmakuList: TimeLineDanmaku[]; 12 | }; 13 | 14 | /** 15 | * 添加一个弹幕 16 | * @param danmaku 17 | * @param start 18 | * @param end 19 | */ 20 | addDanmaku(danmaku: DanmakuItemInterface, start?: number, end?: number): void; 21 | 22 | /** 23 | * 重置弹幕 24 | */ 25 | reset(): boolean; 26 | } 27 | -------------------------------------------------------------------------------- /src/util/UnitTools.ts: -------------------------------------------------------------------------------- 1 | import { I18n } from "../i18n"; 2 | 3 | export class UnitTools{ 4 | /** 5 | * 拼接带单位的长度字符串 6 | * @param num 长度 7 | * @returns 8 | */ 9 | public static lengthSrting(num:number):string{ 10 | return `${num}px`; 11 | } 12 | /** 13 | * 拼接 Matrix3d() 字符串 14 | * @param arr 16位数字 15 | */ 16 | public static Matrix3dString(arr:number[]):string{ 17 | if(arr.length !== 16){ 18 | throw SyntaxError(I18n.t("matrix3d param less than 16")); 19 | } 20 | return "Matrix3d(" + arr.join(",") + ")"; 21 | } 22 | // static UnitFactory(type:string,obj):string{ 23 | 24 | // } 25 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/DanmakuParserFactory.ts: -------------------------------------------------------------------------------- 1 | import { CodeDanmakuParser } from "./codeDanmaku/CodeDanmakuParser"; 2 | import { DanmakuParserInterface } from "./DanmakuParserInterface"; 3 | import { Mode7DanmakuParser } from "./mode7Danmaku/Mode7DanmakuParser"; 4 | 5 | /** 6 | * 弹幕解析器简单工厂 7 | */ 8 | export class DanmakuParserFactory { 9 | protected static parser = { 10 | code: CodeDanmakuParser, 11 | mode7: Mode7DanmakuParser, 12 | }; 13 | public static getInstance(type: string): DanmakuParserInterface { 14 | let instance = this.parser[type]; 15 | if (!instance) { 16 | instance = this.parser["code"]; 17 | } 18 | return new instance(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context/Context"; 2 | import zhCn from "./zh-cn"; 3 | /** 4 | * i18n 国际化函数 5 | */ 6 | export class I18n { 7 | private static tranTxt = { 8 | "zh-cn": zhCn 9 | } 10 | /** 11 | * 当前语言 12 | */ 13 | private static language: string = Context.language; 14 | /** 15 | * 获取翻译,如果不存在,则返回自身 16 | * @param str 需要国际化的字符串 17 | * @returns 翻译结果 18 | */ 19 | public static t(str: string): string { 20 | // 检查字符串是否存在 21 | if (this.tranTxt[this.language] && this.tranTxt[this.language][str]) { 22 | return this.tranTxt[this.language][str]; 23 | } 24 | return str; 25 | } 26 | } -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | .danmaku-containers { 2 | position: relative; 3 | } 4 | .danmaku-containers * { 5 | box-sizing: border-box; 6 | transform-style: preserve-3d; 7 | margin: 0; 8 | padding: 0; 9 | font-size: 40px; 10 | image-rendering: pixelated; /* 小图片放大后像素不虚化 */ 11 | image-rendering: -moz-crisp-edges; 12 | line-height: 1; 13 | font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif; 14 | color: #000; 15 | border: none; 16 | background: none; 17 | } 18 | .danmaku-containers .stage { 19 | position: absolute; 20 | overflow: hidden; 21 | perspective: 1000px; 22 | transform-style: preserve-3d; 23 | } 24 | .danmaku-containers-debug * { 25 | outline: 1px solid red; 26 | } 27 | -------------------------------------------------------------------------------- /src/core/Animation/Base/AnimationInterface.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | 3 | /** 4 | * 动画接口 5 | * 6 | * @export 7 | * @interface AnimationInterface 8 | */ 9 | export interface AnimationInterface{ 10 | /** 11 | * 获取变换矩阵 false 表示完成或无 12 | * @param time 动画开始的时间 13 | */ 14 | getMatrix(time:number):number[]|false 15 | /** 16 | * 获取样式信息 false 表示完成或无 17 | * @param time 18 | */ 19 | getStyle(time:number):DanmakuStyle|false 20 | 21 | /** 22 | * 设置动画参数 23 | * @param json json格式的参数 24 | */ 25 | setParams(param:{[idx: string]:any}):boolean 26 | 27 | /** 28 | * 获取动画总时长 29 | */ 30 | getDuration():number; 31 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/DanmakuPosititon.ts: -------------------------------------------------------------------------------- 1 | import { JsonPreprocessInterface } from "./JsonPreprocessInterface"; 2 | 3 | export class DanmakuPosition implements JsonPreprocessInterface { 4 | public process(json: any, _index: number) { 5 | // 当弹幕不存在动画时 6 | if (!json?.animations || json?.animations === []) { 7 | // 检测弹幕是否存在xyz和duration 8 | let x = json?.x || 0; 9 | let y = json?.y || 0; 10 | let z = json?.z || 0; 11 | let duration = json.duration || 1000; 12 | json.animations = [ 13 | { 14 | type: "static", 15 | duration, 16 | x, 17 | y, 18 | z, 19 | }, 20 | ]; 21 | } 22 | 23 | return json; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | #box{ 2 | border: 1px solid black; 3 | width: 600px; 4 | height: 800px; 5 | box-sizing: border-box; 6 | overflow: hidden; 7 | position: relative; 8 | } 9 | 10 | .block { 11 | width: 100px; 12 | /* height: 100px;*/ 13 | /*animation-name: example; 14 | animation-duration: 4s; 15 | animation-timing-function: linear; */ 16 | transform: translateZ(0) ; 17 | position: absolute; 18 | transition:transform 100ms linear; 19 | 20 | } 21 | 22 | 23 | @keyframes example{ 24 | from { 25 | transform: translateX(600px); 26 | 27 | /* left: 0px; */ 28 | } 29 | to{ 30 | transform: translateX(-100px); 31 | /* left: 100px; */ 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /src/Factory/RendererFactory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 简单渲染器工厂 3 | */ 4 | 5 | import { I18n } from "../i18n"; 6 | import { BaseRenderer } from "../core/Renderer/BaseRenderer"; 7 | import { RendererInterface } from "../core/Renderer/RendererInterface"; 8 | import { CSS3Renderer } from "../core/Renderer/CSS3Renderer/CSS3Renderer"; 9 | 10 | export class RendererFactory{ 11 | public static rendererList = { 12 | base:BaseRenderer, 13 | css3:CSS3Renderer 14 | } 15 | public static getRenderInstance(type:string):RendererInterface{ 16 | if (this.rendererList[type]) { 17 | return new this.rendererList[type]() 18 | }else{ 19 | throw ReferenceError(I18n.t("Renderer type is null")) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/core/Renderer/RendererInterface.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../Danmaku/DanmakuItemInterface"; 2 | import { CanvasStyle } from "../Style/CanvasStyle"; 3 | 4 | /** 5 | * 渲染器接口 6 | */ 7 | export interface RendererInterface{ 8 | /** 9 | * 设置画布容器 10 | * @param canvas 11 | */ 12 | setCanvasContainer(canvas:HTMLElement):boolean; 13 | /** 14 | * 更新画布样式 15 | * @param style 16 | */ 17 | updateCanvasStyle(style:CanvasStyle):boolean; 18 | 19 | /** 20 | * 渲染弹幕 21 | */ 22 | addDanmaku(danmaku:DanmakuItemInterface):boolean 23 | 24 | /** 25 | * 刷新渲染器,会根据刷新率调用 26 | */ 27 | refresh(time:number):boolean 28 | 29 | /** 30 | * 重置渲染器 31 | */ 32 | reset():boolean 33 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/", // 打包到的目录 5 | "sourceMap": true, // 是否生成sourceMap(用于浏览器调试) 6 | "noImplicitAny": false, 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "declaration": false, // 是否生成声明文件 10 | "moduleResolution": "node", 11 | "module": "esnext", 12 | "target": "es5", // 转化成的目标语言 13 | "baseUrl": "./", 14 | "strictNullChecks": true, 15 | "typeRoots": [ 16 | "./node_modules/@types" 17 | ], 18 | "lib": [ 19 | "dom", 20 | "es2015" 21 | ], 22 | "jsx": "react", 23 | "allowJs": false 24 | }, 25 | "include": [ 26 | "src/**/*.ts" 27 | ], // 要打包的文件 28 | "exclude": [ 29 | "node_modules", 30 | "*.test.ts" 31 | ] 32 | } -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/RotationAnimation.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | import { OriginAnimations } from "../Base/OriginAnimations"; 3 | 4 | /** 5 | * 旋转父类 6 | */ 7 | export abstract class RotationAnimation extends OriginAnimations { 8 | 9 | public angle:{start:number,end:number} = {start:0,end:360} 10 | public setParams(param: { [idx: string]: any; }): boolean { 11 | if (!super.setParams(param)) { return false; } 12 | this.angle = Object.assign(this.angle,param?.angle) 13 | return true 14 | } 15 | public getCubicStyle(_progress: number): false | DanmakuStyle { 16 | return false; 17 | } 18 | /** 19 | * 角度转弧度 20 | */ 21 | protected A2R(a: number) { 22 | return a * (Math.PI / 180) 23 | } 24 | } -------------------------------------------------------------------------------- /src/core/Danmaku/DanmakuItemInterface.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../Style/DanmakuStyle"; 2 | import { AnimationInterface } from "../Animation/Base/AnimationInterface"; 3 | 4 | /** 5 | * 单个弹幕接口 6 | */ 7 | export interface DanmakuItemInterface{ 8 | /** 9 | * 弹幕类型 10 | */ 11 | getType():string 12 | /** 13 | * 弹幕内容 14 | */ 15 | getContent():string 16 | /** 17 | * 弹幕子元素 18 | */ 19 | getChild():DanmakuItemInterface[] 20 | /** 21 | * 弹幕动画 22 | */ 23 | getAnimation():AnimationInterface 24 | /** 25 | * 弹幕样式 26 | */ 27 | getStyle():DanmakuStyle 28 | /** 29 | * 设置弹幕参数 30 | * @param param 31 | */ 32 | setParams(param:{[idx: string]:any}):boolean 33 | 34 | /** 35 | * 弹幕开始时间 36 | */ 37 | startTime():number 38 | } -------------------------------------------------------------------------------- /src/core/Style/FontInterface.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "./Unit/Color"; 2 | import { Shadow } from "./Unit/Shadow"; 3 | import { PxSize } from "./Unit/PxSize"; 4 | 5 | export interface FontInterface{ 6 | /** 7 | * 字体样式 8 | */ 9 | fontStyle?:Fontstyle 10 | /** 11 | * 字体粗细 12 | */ 13 | fontWeight?:number 14 | /** 15 | * 字体类型 16 | */ 17 | fontFamily?:string 18 | /** 19 | * 字体大小 20 | */ 21 | fontSize?:PxSize 22 | /** 23 | * 字体颜色 24 | */ 25 | color?:Color 26 | /** 27 | * 字体阴影 28 | */ 29 | textShadow?:Shadow 30 | } 31 | export enum Fontstyle { 32 | /** 33 | * 正常 34 | */ 35 | normal="normal", 36 | /** 37 | * 斜体 38 | */ 39 | italic="italic", 40 | /** 41 | * 随父容器 42 | */ 43 | inherit="inherit" 44 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mf-ade", 3 | "version": "1.8.4", 4 | "description": "基于TS的高级弹幕引擎", 5 | "main": "dist/index.js", 6 | "types": "src/index.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "webpack --mode=production && tsc", 10 | "watch": "webpack --watch --mode=development", 11 | "lint:fix": "tslint -c tslint.json 'src/**/*.ts' --fix" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/core": "^7.15.0", 17 | "babel-loader": "^8.2.2", 18 | "css-loader": "^5.2.7", 19 | "file-loader": "^6.2.0", 20 | "style-loader": "^2.0.0", 21 | "ts-loader": "^9.2.5", 22 | "tslint": "^6.1.3", 23 | "typescript": "^4.4.2", 24 | "url-loader": "^4.1.1", 25 | "webpack": "^5.51.1", 26 | "webpack-cli": "^4.8.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/DanmakuValidate.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuEvent } from "../../../Event/DanmakuEvent"; 2 | import { DanmakuEventType } from "../../../Event/DanmakuEventType"; 3 | import { I18n } from "../../../i18n"; 4 | import { JsonPreprocessInterface } from "./JsonPreprocessInterface"; 5 | 6 | /** 7 | * 判断弹幕类型是否正确 8 | */ 9 | export class DanmakuValidate implements JsonPreprocessInterface { 10 | public process(json: any, index: number) { 11 | // 判断弹幕是否是object 12 | if (typeof json !== "object") { 13 | console.warn( 14 | `${I18n.t("Unknown danmaku format")} : ${json} index: + ${index}` 15 | ); 16 | // 触发弹幕类型错误事件 17 | DanmakuEvent.dispatch(DanmakuEventType.DANMAKU_FORMAT_ERROR, { 18 | content: json, 19 | index, 20 | }); 21 | return false; 22 | } 23 | return json; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/PostprocessPipe.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../../../core/Danmaku/DanmakuItemInterface"; 2 | import { DanmakuDelay } from "./DanmakuDelay"; 3 | import { DanmakuThen } from "./DanmakuThen"; 4 | import { JsonPostprocessInterface } from "./JsonPostprocessInterface"; 5 | /** 6 | * 后渲染管道 7 | */ 8 | export class PostprocessPipe implements JsonPostprocessInterface { 9 | protected list: JsonPostprocessInterface[] = [ 10 | new DanmakuThen(), // then 语法处理 11 | new DanmakuDelay(), // delay 语法处理 12 | ]; 13 | public process( 14 | danmaku: DanmakuItemInterface, 15 | params: any 16 | ): DanmakuItemInterface | false { 17 | let dan = danmaku; 18 | this.list.forEach((value) => { 19 | let dan2 = value.process(danmaku, params); 20 | // 如果中途有任何一个环节出错,就退出 21 | if (dan2 === false) { 22 | return false; 23 | } 24 | dan = dan2; 25 | }); 26 | return dan; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/DanmakuThen.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../../../core/Danmaku/DanmakuItemInterface"; 2 | import { JsonPostprocessInterface } from "./JsonPostprocessInterface"; 3 | 4 | /** 5 | * Then 语法处理 6 | */ 7 | export class DanmakuThen implements JsonPostprocessInterface { 8 | protected idList: number[] = []; 9 | public process( 10 | danmaku: DanmakuItemInterface, 11 | params: any 12 | ): false | DanmakuItemInterface { 13 | // 设置开始时间 14 | if (!params?.start && params?.then) { 15 | let start = this.idList[params.then] || 0; 16 | danmaku.setParams({ 17 | start, 18 | }); 19 | } 20 | // 计算存在id属性结束时间 21 | if (params?.id) { 22 | this.idList[params.id] = 23 | danmaku.startTime() + danmaku.getAnimation().getDuration(); 24 | } 25 | // _LAST_ 变量 26 | this.idList["_LAST_"] = 27 | danmaku.startTime() + danmaku.getAnimation().getDuration(); 28 | return danmaku; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/Animation/styleAnimations/OpacityAnimations.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | import { CubicAnimations } from "../Base/CubicAnimations"; 3 | 4 | /** 5 | * 透明度动画 6 | * 7 | * @export 8 | * @class OpacityAnimations 9 | * @extends {CubicAnimations} 10 | */ 11 | export class OpacityAnimations extends CubicAnimations { 12 | public opacity: [number, number] = [0, 1]; 13 | public setParams(param?: { [idx: string]: any }): boolean { 14 | super.setParams(param); 15 | this.opacity = param?.opacity || this.opacity; 16 | // console.log(param); 17 | return true; 18 | } 19 | public getCubicStyle(progress: number, _time: number): false | DanmakuStyle { 20 | return { 21 | opacity: this.getProgressValue( 22 | this.opacity[0], 23 | this.opacity[1], 24 | progress 25 | ), 26 | }; 27 | } 28 | public getMatrixForCubic(_progress: number, _time: number): false | number[] { 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/core/Style/Unit/Shadow.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "./Color"; 2 | import { PxSize } from "./PxSize"; 3 | import { UnitInterface } from "./UnitInterface"; 4 | 5 | export class Shadow implements UnitInterface { 6 | constructor( 7 | h: PxSize, 8 | v: PxSize, 9 | blur: PxSize, 10 | color: Color, 11 | inset?: boolean 12 | ) { 13 | this.h = h; 14 | this.v = v; 15 | this.blur = blur 16 | this.color = color 17 | this.inset = inset 18 | 19 | } 20 | public string(): string { 21 | let str = this.h.string() + " " + 22 | this.v.string() + " " + 23 | this.blur.string() + " " + 24 | this.color.string() + " "; 25 | if(this.inset){ 26 | str += " inset" 27 | } 28 | return str; 29 | } 30 | // 水平阴影 31 | public h: PxSize 32 | // 垂直阴影 33 | public v: PxSize 34 | // 深度 35 | public blur: PxSize 36 | // 颜色 37 | public color: Color 38 | // 内阴影 39 | public inset?: boolean 40 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/PreprocessPipe.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuDefaultAttr } from "./DanmakuDefaultAttr"; 2 | import { DanamkuExtends } from "./DanmakuExtends"; 3 | import { DanmakuPosition } from "./DanmakuPosititon"; 4 | import { DanmakuValidate } from "./DanmakuValidate"; 5 | import { JsonPreprocessInterface } from "./JsonPreprocessInterface"; 6 | 7 | /** 8 | * 弹幕预渲染管道 9 | */ 10 | export class PreprocessPipe implements JsonPreprocessInterface { 11 | protected list: JsonPreprocessInterface[] = [ 12 | new DanmakuValidate(), // 弹幕格式验证 13 | new DanamkuExtends(), // 弹幕继承模块 14 | new DanmakuPosition(), // 弹幕xyz属性语法糖 15 | new DanmakuDefaultAttr(), // 弹幕默认属性设置 16 | ]; 17 | public process(json: any, index: number) { 18 | // 初始化当前的弹幕 19 | let danmaku: any = json; 20 | this.list.forEach((value) => { 21 | let tmpDanmaku = value.process(danmaku, index); 22 | // 如果中途有任何一个环节出错,就退出 23 | if (tmpDanmaku === false) { 24 | return false; 25 | } 26 | danmaku = tmpDanmaku; 27 | }); 28 | return danmaku; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Event/DanmakuEvent.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuEventType } from "./DanmakuEventType"; 2 | 3 | /** 4 | * 弹幕事件工具 5 | */ 6 | export class DanmakuEvent { 7 | // 事件前缀,防止冲突 8 | public static prefix = "_danmakuevent_"; 9 | 10 | /** 11 | * 包装事件名称字符串 12 | * @param eventname 事件名称 13 | * @returns 14 | */ 15 | protected static buildPrefix(eventname: DanmakuEventType): string { 16 | return this.prefix + eventname; 17 | } 18 | /** 19 | * 监听弹幕事件 20 | * @param eventname 21 | * @param callback 22 | */ 23 | public static listener(eventname: DanmakuEventType, callback: (data: T) => void) { 24 | // 封装浏览器提供的事件监听器 25 | window.addEventListener(this.buildPrefix(eventname), (data: any) => { 26 | callback(data.detail); 27 | }); 28 | } 29 | 30 | /** 31 | * 触发弹幕事件 32 | * @param eventname 33 | * @param data 34 | */ 35 | public static dispatch(eventname: DanmakuEventType, data: T) { 36 | // 包装事件 37 | let event = new CustomEvent(this.buildPrefix(eventname), { 38 | detail: data, 39 | }); 40 | // 触发事件 41 | window.dispatchEvent(event); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/StaticAnimation.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | import { AnimationInterface } from "../Base/AnimationInterface"; 3 | 4 | /** 5 | * 静止动画 仅仅悬停在某一个位置一定时间 6 | */ 7 | export class StaticAnimation implements AnimationInterface { 8 | public getDuration(): number { 9 | return this.duration; 10 | } 11 | 12 | public duration: number = 1000; 13 | public x: number = 0; 14 | public y: number = 0; 15 | public z: number = 0; 16 | public setParams(param: { [idx: string]: any }): boolean { 17 | let def = 0; 18 | this.duration = 19 | param?.duration !== undefined ? param?.duration : this.duration; 20 | this.x = param?.x || def; 21 | this.y = param?.y || def; 22 | this.z = param?.z || def; 23 | return true; 24 | } 25 | public getMatrix(time: number): false | number[] { 26 | // console.log(time); 27 | 28 | if (time > this.duration) { 29 | return false; 30 | } 31 | 32 | return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, this.x, this.y, this.z, 1]; 33 | } 34 | public getStyle(): false | DanmakuStyle { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/ScaleAnimations.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | import { OriginAnimations } from "../Base/OriginAnimations"; 3 | 4 | /** 5 | * 缩放动画 6 | */ 7 | export class ScaleAnimations extends OriginAnimations{ 8 | public scale:{x1:number,y1:number,z1:number,x2:number,y2:number,z2:number} 9 | = {x1:1,y1:1,z1:1,x2:1,y2:1,z2:1} 10 | public setParams(param: { [idx: string]: any; }): boolean { 11 | if (!super.setParams(param)) { return false; } 12 | this.scale = Object.assign(this.scale,param?.scale) 13 | return true 14 | } 15 | public getMatrixForOrigin(progress: number): false | number[] { 16 | let x = this.getProgressValue(this.scale.x1, this.scale.x2, progress); 17 | let y = this.getProgressValue(this.scale.y1, this.scale.y2, progress) 18 | let z = this.getProgressValue(this.scale.z1, this.scale.z2, progress) 19 | let m = [ 20 | x,0,0,0, 21 | 0,y,0,0, 22 | 0,0,z,0, 23 | 0,0,0,1 24 | ] 25 | return m 26 | 27 | } 28 | public getCubicStyle(_progress: number): false | DanmakuStyle { 29 | return false 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/DanmakuExtends.ts: -------------------------------------------------------------------------------- 1 | import { deepMerge } from "../../../util/deepMerge"; 2 | import { JsonPreprocessInterface } from "./JsonPreprocessInterface"; 3 | /** 4 | * 弹幕继承 5 | */ 6 | export class DanamkuExtends implements JsonPreprocessInterface { 7 | /** 8 | * id列表 9 | */ 10 | protected idList: any[] = []; 11 | public process(json: any, _index: number) { 12 | let danmaku = json; 13 | // 继承弹幕 14 | if (danmaku?.extends) { 15 | // 如果id和extend相等,直接返回 16 | if (danmaku?.id === danmaku?.extends) { 17 | return json; 18 | } 19 | // 获取父级弹幕 20 | 21 | let parent = this.idList[json.extends]; 22 | if (parent) { 23 | // 合并 24 | danmaku = deepMerge(this.copy(parent), json); 25 | } 26 | } 27 | 28 | // 标记弹幕id 29 | if (json?.id) { 30 | this.save(json.id, danmaku); 31 | } 32 | // 存储_LAST_变量 33 | this.save("_LAST_", danmaku); 34 | return danmaku; 35 | } 36 | protected copy(json: any) { 37 | return JSON.parse(JSON.stringify(json)); 38 | } 39 | protected save(name: string, danmaku: any) { 40 | // 如果项目存在id则加入到列表 41 | let copy = this.copy(danmaku); 42 | // 去掉id属性 43 | delete copy.id; 44 | this.idList[name] = copy; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/Animation/TransformsAnimations/TranslateAnimation.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | import { CubicAnimations } from "../Base/CubicAnimations"; 3 | 4 | export class TranslateAnimation extends CubicAnimations { 5 | 6 | public path: { x1: number, y1: number, z1: number, x2: number, y2: number, z2: number } 7 | = 8 | { x1: 0, y1: 0, x2: 0, y2: 0, z1: 0, z2: 0 } 9 | public setParams(param: { [idx: string]: any; }): boolean { 10 | if (!super.setParams(param)) { return false; } 11 | this.path = Object.assign(this.path,param?.path) 12 | return true 13 | } 14 | public getCubicStyle(): false | DanmakuStyle { 15 | return false; 16 | } 17 | public getMatrixForCubic(progress: number): false | number[] { 18 | // console.log(this.path); 19 | // console.log(progress); 20 | 21 | let x = this.getProgressValue(this.path.x1, this.path.x2, progress); 22 | let y = this.getProgressValue(this.path.y1, this.path.y2, progress) 23 | let z = this.getProgressValue(this.path.z1, this.path.z2, progress) 24 | 25 | return [ 26 | 1, 0, 0, 0, 27 | 0, 1, 0, 0, 28 | 0, 0, 1, 0, 29 | x, y, z, 1 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /src/core/Renderer/BaseRenderer.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { DanmakuItemInterface } from "../Danmaku/DanmakuItemInterface"; 4 | import { CanvasStyle } from "../Style/CanvasStyle"; 5 | import { RendererInterface } from "./RendererInterface"; 6 | 7 | export class BaseRenderer implements RendererInterface { 8 | public reset(): boolean { 9 | throw new Error("Method not implemented."); 10 | } 11 | public addDanmaku(_danmaku: DanmakuItemInterface): boolean { 12 | return false; 13 | } 14 | public refresh(): boolean { 15 | return false 16 | } 17 | protected canvas?: HTMLElement 18 | public setCanvasContainer(canvas: HTMLElement): boolean { 19 | this.canvas = canvas 20 | return true 21 | } 22 | public updateCanvasStyle(style: CanvasStyle): boolean { 23 | if (this.canvas) { 24 | this.canvas.style.width = style.size.width.string(); 25 | this.canvas.style.height = style.size.height.string(); 26 | this.canvas.style.left = style.position.x.string() 27 | this.canvas.style.top = style.position.y.string() 28 | this.canvas.style.backgroundColor = style.color.string() 29 | return true 30 | } else { 31 | return false 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: { 5 | mfunsAdvancedDanmaku: "./src/index.ts", 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, "lib"), 9 | filename: "[name].js", 10 | publicPath: "lib/", 11 | }, 12 | devtool: "source-map", 13 | resolve: { 14 | extensions: [".js", ".ts", ".vue", ".json"], 15 | alias: { 16 | src: path.join(__dirname, "./src"), // 这样@符号就表示项目根目录中src这一层路径 17 | }, 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.css$/i, 23 | use: ["style-loader", "css-loader"], 24 | }, 25 | { 26 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 27 | use: [ 28 | { 29 | loader: "url-loader", 30 | options: { 31 | limit: 89192, 32 | name: "img/[name].[hash:8].[ext]", 33 | }, 34 | }, 35 | ], 36 | }, 37 | { 38 | test: /\.js$/, 39 | loader: "babel-loader", 40 | }, 41 | { 42 | test: /\.tsx?$/, 43 | use: [ 44 | { 45 | loader: "ts-loader", 46 | options: { 47 | transpileOnly: true, 48 | }, 49 | }, 50 | ], 51 | }, 52 | ], 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /src/core/Style/Unit/Color.ts: -------------------------------------------------------------------------------- 1 | import { UnitInterface } from "./UnitInterface" 2 | 3 | /** 4 | * 颜色接口 5 | */ 6 | 7 | export class Color implements UnitInterface { 8 | public r: number 9 | public g: number 10 | public b: number 11 | public a: number 12 | constructor(r:number, g:number, b:number, a:number) { 13 | this.r = r 14 | this.g = g 15 | this.b = b 16 | this.a = a 17 | } 18 | public string(): string { 19 | return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")" 20 | } 21 | /** 22 | * 常见颜色表 23 | */ 24 | protected static color:{[key:string]:[number,number,number,number]} = { 25 | "black":[0,0,0,1], 26 | "white":[255,255,255,1], 27 | "red":[255,0,0,1], 28 | "green":[0,255,0,1], 29 | "blue":[0,0,255,1], 30 | "pink":[255,105,108,1], 31 | "yellow":[255,255,0,1], 32 | "grey":[130,130,130,1] 33 | } 34 | /** 35 | * 获取预设颜色 36 | * @param color 颜色名称 37 | * @returns 38 | */ 39 | public static getColor(color:string):Color{ 40 | if(this.color[color]){ 41 | return new this(...this.color[color]) 42 | }else{ 43 | return new this(...this.color["black"]) 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | time 18 |
19 | 20 | 21 | 42 | 43 | -------------------------------------------------------------------------------- /src/core/Animation/Base/OriginAnimations.ts: -------------------------------------------------------------------------------- 1 | import { CubicAnimations } from "./CubicAnimations"; 2 | import { Matrix } from "./Matrix"; 3 | 4 | /** 5 | * 可改变动画中心点的动画 6 | * 7 | * @export 8 | * @abstract 9 | * @class OriginAnimations 10 | * @extends {CubicAnimations} 11 | */ 12 | export abstract class OriginAnimations extends CubicAnimations { 13 | public origin:number[] = [0,0,0] 14 | public setParams(param: { [idx: string]: any; }): boolean { 15 | if (!super.setParams(param)) { return false; } 16 | this.origin = param?.origin || this.origin 17 | return true 18 | } 19 | public getMatrixForCubic(progress: number): number[] | false{ 20 | let arr = this.getMatrixForOrigin(progress) 21 | if(!arr) { return false } 22 | arr = Matrix.mult([ 23 | 1, 0, 0, 0, 24 | 0, 1, 0, 0, 25 | 0, 0, 1, 0, 26 | -this.origin[0], -this.origin[1], -this.origin[2], 1 27 | 28 | ], arr) 29 | return Matrix.mult(arr,[ 30 | 1, 0, 0, 0, 31 | 0, 1, 0, 0, 32 | 0, 0, 1, 0, 33 | this.origin[0], this.origin[1], this.origin[2], 1 34 | 35 | ]) 36 | } 37 | /** 38 | * 获取用于计算原坐标点的矩阵 39 | * @param progress 40 | */ 41 | public abstract getMatrixForOrigin(progress: number): number[] | false 42 | } -------------------------------------------------------------------------------- /src/core/Animation/Base/Cubic.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 贝塞尔相关工具函数 3 | */ 4 | export class Cubic { 5 | public px3: number 6 | public px2: number 7 | public px1: number 8 | public py3: number 9 | public py2: number 10 | public py1: number 11 | public epsilon: number 12 | constructor (a: number, b: number, c: number, d: number) { 13 | this.px3 = 3 * a 14 | this.px2 = 3 * (c - a) - this.px3 15 | this.px1 = 1 - this.px3 - this.px2 16 | this.py3 = 3 * b 17 | this.py2 = 3 * (d - b) - this.py3 18 | this.py1 = 1 - this.py3 - this.py2 19 | this.epsilon = 1e-7 // 目标精度 20 | } 21 | 22 | public getX(t: number) { 23 | return ((this.px1 * t + this.px2) * t + this.px3) * t 24 | } 25 | 26 | public getY(t: number) { 27 | return ((this.py1 * t + this.py2) * t + this.py3) * t 28 | } 29 | 30 | public solve(x: number) { 31 | if (x === 0 || x === 1) { // 对 0 和 1 两个特殊 t 不做计算 32 | return this.getY(x) 33 | } 34 | let t = x 35 | for (let i = 0; i < 8; i++) { // 进行 8 次迭代 36 | let g = this.getX(t) - x 37 | if (Math.abs(g) < this.epsilon) { // 检测误差到可以接受的范围 38 | return this.getY(t) 39 | } 40 | let d = (3 * this.px1 * t + 2 * this.px2) * t + this.px3 // 对 x 求导 41 | if (Math.abs(d) < 1e-6) { // 如果梯度过低,说明牛顿迭代法无法达到更高精度 42 | break 43 | } 44 | t = t - g / d 45 | } 46 | return this.getY(t) // 对得到的近似 t 求 y 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/core/Stage/StageInterface.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "../Style/Unit/Color"; 2 | import { PositionInterface } from "../Style/PositionInterface"; 3 | import { RendererInterface } from "../Renderer/RendererInterface"; 4 | import { SizeInterface } from "../Style/SizeInterface"; 5 | import { TimeLineInterface } from "../../TimeLine/TimeLineInterface"; 6 | 7 | export interface StageInterface { 8 | /** 9 | * 获取舞台位置 10 | * @param containersSize 容器的尺寸 11 | * @param stageSize 舞台的尺寸 12 | */ 13 | stagePosition( 14 | containersSize: SizeInterface, 15 | stageSize: SizeInterface 16 | ): PositionInterface; 17 | /** 18 | * 获取舞台背景 19 | * @param containersSize 容器尺寸 20 | */ 21 | stageBackgroundColor(containersSize: SizeInterface): Color; 22 | /** 23 | * 获取舞台的尺寸 24 | * @param containersSize 容器的尺寸 25 | */ 26 | stageSize(containersSize: SizeInterface): SizeInterface; 27 | 28 | /** 29 | * 接收渲染器对象 30 | * @param render 渲染器对象 31 | */ 32 | stageRenderer(render: RendererInterface): boolean; 33 | 34 | /** 35 | * 设置时间轴对象 36 | */ 37 | timeLine(timeLine: TimeLineInterface): boolean; 38 | 39 | /** 40 | * 获取时间轴对象 41 | */ 42 | getTimeLine(): TimeLineInterface; 43 | /** 44 | * 获取渲染器对象 45 | */ 46 | getRenderer(): RendererInterface | null; 47 | 48 | /** 49 | * 刷新舞台 50 | */ 51 | refresh(time: number): boolean; 52 | 53 | /** 54 | * 期望的渲染器类型 55 | */ 56 | rendererType(): string; 57 | 58 | /** 59 | * 期望的弹幕时间轴类型 60 | */ 61 | timeLineType(): string; 62 | /** 63 | * 期望的附属组件类型 64 | */ 65 | attachedType(): string; 66 | /** 67 | * 重置 68 | */ 69 | reset(): boolean; 70 | } 71 | -------------------------------------------------------------------------------- /src/core/Danmaku/BaseDanmaku.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../Style/DanmakuStyle"; 2 | import { AnimationInterface } from "../Animation/Base/AnimationInterface"; 3 | import { DanmakuItemInterface } from "./DanmakuItemInterface"; 4 | import { StaticAnimation } from "../Animation/TransformsAnimations/StaticAnimation"; 5 | 6 | /** 7 | * 基础弹幕类型 8 | */ 9 | export class BaseDanmaku implements DanmakuItemInterface { 10 | public style: DanmakuStyle = {}; 11 | public start: number = 0; 12 | public animation?: AnimationInterface; 13 | public content: string = ""; 14 | public child: DanmakuItemInterface[] = []; 15 | /** 16 | * 设置参数 17 | * @param param 18 | * @returns 19 | */ 20 | public setParams(param: { [idx: string]: any }): boolean { 21 | // 设置文字样式 22 | try { 23 | // 设置开始时间 24 | if (param?.start) { 25 | this.start = param?.start; 26 | } 27 | if (param?.animation) { 28 | this.animation = param?.animation || new StaticAnimation();} 29 | if (param?.child) { this.child = param.child; } 30 | if (param?.style) { this.style = param?.style; } 31 | if (param?.content) { this.content = param?.content;} 32 | } catch (e) { 33 | console.warn(e); 34 | return false; 35 | } 36 | 37 | return true; 38 | } 39 | public getType(): string { 40 | return "base"; 41 | } 42 | public getContent(): string { 43 | return this.content; 44 | } 45 | public getChild(): DanmakuItemInterface[] { 46 | return this.child; 47 | } 48 | public getAnimation(): AnimationInterface { 49 | if (!this.animation) { 50 | this.animation = new StaticAnimation(); 51 | } 52 | return this.animation; 53 | } 54 | public getStyle(): DanmakuStyle { 55 | return this.style; 56 | } 57 | public startTime(): number { 58 | return this.start; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/core/Animation/Base/Matrix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 矩阵变换相关工具函数 3 | */ 4 | export class Matrix { 5 | /** 6 | * 加法 7 | * @param a 8 | * @param b 9 | */ 10 | public static add(a: number[], b: number[]): number[] { 11 | let c: number[] = [] 12 | for (let i = 0; i < a.length; i++) { 13 | c[i] = a[i] + b[i] 14 | } 15 | return c 16 | } 17 | /** 18 | * 减法 19 | * @param a 20 | * @param b 21 | * @returns 22 | */ 23 | public static sub(a: number[], b: number[]): number[] { 24 | let c: number[] = [] 25 | for (let i = 0; i < a.length; i++) { 26 | c[i] = a[i] - b[i] 27 | } 28 | return c 29 | } 30 | public static mult(a: number[], b: number[]): number[] { 31 | let a1 = this.tran(a); 32 | let b1 = this.tran(b); 33 | // 相乘约束 34 | if (a1[0].length !== b1.length) { 35 | throw new Error(); 36 | } 37 | let m = a1.length; 38 | let p = a1[0].length; 39 | let n = b1[0].length; 40 | 41 | // 初始化 m*n 全 0 二维数组 42 | let c = new Array(m).fill(0).map(()=> new Array(n).fill(0)); 43 | 44 | for (let i = 0; i < m; i++) { 45 | for (let j = 0; j < n; j++) { 46 | for (let k = 0; k < p; k++) { 47 | c[i][j] += a1[i][k] * b1[k][j]; 48 | } 49 | } 50 | } 51 | 52 | return [...c[0],...c[1],...c[2],...c[3]]; 53 | } 54 | /** 55 | * 将一维16位数组转换成4*4二维数组 56 | * @param a 57 | */ 58 | protected static tran(a: number[]):number[][] { 59 | let arr: number[][] = [] 60 | for (let i = 0; i < 4; i++) { 61 | for (let j = 0; j < 4; j++) { 62 | let index = i * 4 + j 63 | if(!arr[i]) { arr[i] = []; } 64 | arr[i][j] = a[index]; 65 | } 66 | } 67 | return arr; 68 | } 69 | /** 70 | * 获得一个无效果的4x4矩阵 71 | */ 72 | public static getNullMatrix():number[]{ 73 | return [ 74 | 1,0,0,0, 75 | 0,1,0,0, 76 | 0,0,1,0, 77 | 0,0,0,1 78 | ] 79 | } 80 | } -------------------------------------------------------------------------------- /src/core/Animation/CompositeAnimation/RepeatAnimations.ts: -------------------------------------------------------------------------------- 1 | import { AnimationFactory } from "../../../Factory/AnimationFactory"; 2 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 3 | import { AnimationInterface } from "../Base/AnimationInterface"; 4 | import { CubicAnimations } from "../Base/CubicAnimations"; 5 | import { StaticAnimation } from "../TransformsAnimations/StaticAnimation"; 6 | 7 | export class RepeatAnimations extends CubicAnimations { 8 | /** 9 | * 动画接口 10 | */ 11 | public animation: AnimationInterface; 12 | /** 13 | * 重复次数 14 | */ 15 | public repeat: number = 1; 16 | /** 17 | * 单个动画时间 18 | */ 19 | public animationTime: number; 20 | public duration: number = 0; 21 | public getCubicStyle(_progress: number, time: number): false | DanmakuStyle { 22 | return this.animation.getStyle(this.getCurTime(time)); 23 | } 24 | public getMatrixForCubic(_progress: number, time: number): false | number[] { 25 | return this.animation.getMatrix(this.getCurTime(time)); 26 | } 27 | public setParams(param: { [idx: string]: any }): boolean { 28 | super.setParams(param); 29 | 30 | if (param?.animations) { 31 | let animation = AnimationFactory.getAnimations("list", { 32 | animations: param?.animations, 33 | }); 34 | if (animation) { 35 | this.animation = animation; 36 | } else { 37 | this.animation = new StaticAnimation(); 38 | } 39 | // // 将列表组成一个list 40 | // this.animation = 41 | // AnimationFactory.getAnimations( 42 | // param?.animation.type, 43 | // param?.animations 44 | // ) || new StaticAnimation(); 45 | } else { 46 | this.animation = new StaticAnimation(); 47 | } 48 | 49 | // console.log(this.animation); 50 | 51 | this.repeat = param?.repeat || this.repeat; 52 | this.animationTime = this.animation.getDuration(); 53 | // 覆盖父级存活时间 54 | // 计算方式 单个弹幕时间 X 弹幕重复次数 55 | this.duration = this.animationTime * this.repeat; 56 | // console.log(this.duration); 57 | 58 | return true; 59 | } 60 | /** 61 | * 获得当前的弹幕时间 62 | */ 63 | public getCurTime(time: number) { 64 | // 取余 65 | return time % this.animationTime; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Factory/AnimationFactory.ts: -------------------------------------------------------------------------------- 1 | import { AnimationInterface } from "../core/Animation/Base/AnimationInterface"; 2 | import { GroupAnimations } from "../core/Animation/CompositeAnimation/GroupAnimations"; 3 | import { ListAnimations } from "../core/Animation/CompositeAnimation/ListAnimations"; 4 | import { OpacityAnimations } from "../core/Animation/styleAnimations/OpacityAnimations"; 5 | import { RepeatAnimations } from "../core/Animation/CompositeAnimation/RepeatAnimations"; 6 | import { ScaleAnimations } from "../core/Animation/TransformsAnimations/ScaleAnimations"; 7 | import { TranslateAnimation } from "../core/Animation/TransformsAnimations/TranslateAnimation"; 8 | import { RotationXAnimation } from "../core/Animation/TransformsAnimations/RotationXAnimation"; 9 | import { RotationYAnimation } from "../core/Animation/TransformsAnimations/RotationYAnimation"; 10 | import { RotationZAnimation } from "../core/Animation/TransformsAnimations/RotationZAnimation"; 11 | import { StaticAnimation } from "../core/Animation/TransformsAnimations/StaticAnimation"; 12 | 13 | export class AnimationFactory { 14 | public static animationList = { 15 | "static":StaticAnimation,// 静态定位 16 | "translate":TranslateAnimation,// 平移 17 | "rotateX":RotationXAnimation,// x轴旋转 18 | "rotateY":RotationYAnimation,// y轴旋转 19 | "rotateZ":RotationZAnimation,// z轴旋转 20 | "scale":ScaleAnimations,// 缩放 21 | "list":ListAnimations,// 动画列表 22 | "group":GroupAnimations,// 动画组 23 | "repeat":RepeatAnimations,// 重复动画 24 | "opacity":OpacityAnimations 25 | } 26 | 27 | public static getAnimations(type:string,params):AnimationInterface | false{ 28 | if(this.animationList[type]){ 29 | // console.log(params); 30 | let ani = new this.animationList[type]() 31 | // console.log(ani); 32 | 33 | ani.setParams(params) 34 | return ani; 35 | } 36 | return false; 37 | } 38 | public static getAnimationsList(list:{key: { [idx: string]: any; }}[]):AnimationInterface[]{ 39 | let animationList:AnimationInterface[] = [] 40 | 41 | list?.forEach((val)=>{ 42 | let ani = this.getAnimations(val["type"],val) 43 | if(ani){ 44 | animationList.push(ani); 45 | } 46 | }) 47 | return animationList 48 | } 49 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "../css/base.css"; 2 | import { Controller } from "./Controller/Controller"; 3 | import { I18n } from "./i18n"; 4 | import { InitConfigInterface } from "./InitConfigInterface"; 5 | import { DanmakuEvent } from "./Event/DanmakuEvent"; 6 | import { DanmakuEventType } from "./Event/DanmakuEventType"; 7 | import { CodeDanmakuStage } from "./core/Stage/CodeDanmakuStage"; 8 | import { Mode7Stage } from "./core/Stage/Mode7Stage"; 9 | export class MFADE { 10 | public controller: Controller; 11 | constructor(config: InitConfigInterface) { 12 | // 类型检查 13 | if (!config.containers) { 14 | throw ReferenceError(I18n.t("Containers is null")); 15 | } 16 | this.controller = new Controller(config.containers); 17 | // json代码弹幕 18 | if (config.codeDanmaku) { 19 | this.controller.addGetDanmakuFunction("code", config.codeDanmaku); 20 | this.controller.registStage(new CodeDanmakuStage(), 1); 21 | } 22 | 23 | // mode7 弹幕 24 | if (config.mode7Danmaku) { 25 | this.controller.addGetDanmakuFunction("mode7", config.mode7Danmaku); 26 | this.controller.registStage(new Mode7Stage(), 2); 27 | } 28 | // 挂载 29 | this.controller.mount(); 30 | // 监听大小变化 31 | window.addEventListener("resize", () => { 32 | this.controller.resize(); 33 | }); 34 | } 35 | 36 | /** 37 | * 暂停 38 | */ 39 | public pause() { 40 | this.controller.pause(); 41 | } 42 | /** 43 | * 开始 44 | */ 45 | public start() { 46 | this.controller.start(); 47 | } 48 | /** 49 | * 跳转 50 | */ 51 | public skip(time: number) { 52 | this.controller.skip(time); 53 | } 54 | /** 55 | * 当前时间 56 | * @returns number 57 | */ 58 | public time() { 59 | return this.controller.getTime(); 60 | } 61 | /** 62 | * 重置弹幕尺寸 63 | */ 64 | public resize() { 65 | this.controller.resize(); 66 | } 67 | /** 68 | * 重置弹幕 69 | */ 70 | public reset() { 71 | this.controller.resetDanmaku(1); 72 | this.controller.reset(); 73 | } 74 | /** 75 | * 添加事件监听 76 | * @param event 77 | * @param callback 78 | */ 79 | public listenerEvent(event: DanmakuEventType, callback: (data: any) => void) { 80 | DanmakuEvent.listener(event, callback); 81 | } 82 | } 83 | 84 | // 添加进全局 85 | if (typeof globalThis !== "undefined") { 86 | globalThis.MFADE = MFADE; 87 | } else { 88 | window["MFADE"] = MFADE; 89 | } 90 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/codeDanmaku/CodeDanmakuParser.ts: -------------------------------------------------------------------------------- 1 | import { I18n } from "../../../i18n"; 2 | import { DanmakuItemInterface } from "../../../core/Danmaku/DanmakuItemInterface"; 3 | import { AnimationFactory } from "../../AnimationFactory"; 4 | import { DanmakuFactory } from "../DanmakuFactory"; 5 | import { DanmakuParserInterface } from "../DanmakuParserInterface"; 6 | import { DanmakuEvent } from "../../../Event/DanmakuEvent"; 7 | import { DanmakuEventType } from "../../../Event/DanmakuEventType"; 8 | import { PreprocessPipe } from "./PreprocessPipe"; 9 | import { PostprocessPipe } from "./PostprocessPipe"; 10 | 11 | /** 12 | * JSON 格式弹幕解析器 13 | */ 14 | export class CodeDanmakuParser implements DanmakuParserInterface { 15 | /** 16 | * 弹幕tag列表 17 | */ 18 | protected tagList: any[] = []; 19 | 20 | public parser(content: string): DanmakuItemInterface[] { 21 | try { 22 | let json: any[] = JSON.parse(content); 23 | return this.getDanmaku(json); 24 | } catch (e) { 25 | // 触发弹幕格式无效事件 26 | DanmakuEvent.dispatch( 27 | DanmakuEventType.DANMAKU_JSON_INVALID, 28 | I18n.t("Danmaku parser fail") 29 | ); 30 | console.warn(I18n.t("Danmaku parser fail")); 31 | console.warn(e); 32 | return []; 33 | } 34 | } 35 | 36 | /** 37 | * 获取弹幕 38 | * @param obj 39 | * @returns 40 | */ 41 | public getDanmaku(obj?: any[]): DanmakuItemInterface[] { 42 | let list: DanmakuItemInterface[] = []; 43 | let preprocessPipe = new PreprocessPipe(); 44 | let postprocessPipe = new PostprocessPipe(); 45 | /** 46 | * 遍历弹幕列表 47 | */ 48 | obj?.forEach((json, index) => { 49 | // 经过前置管道处理 50 | let dan = preprocessPipe.process(json, index); 51 | if (dan) { 52 | // 封装 弹幕对象 53 | let danmaku = DanmakuFactory.getDanmakuInstance(dan.type); 54 | danmaku.setParams({ 55 | start: dan?.start, 56 | content: dan?.content, 57 | style: dan?.style, 58 | animation: AnimationFactory.getAnimations("list", { 59 | animations: dan?.animations, 60 | }), // 将列表组成一个list 61 | child: this.getDanmaku(dan.childs), 62 | }); 63 | // 后置管道处理 64 | // console.log(danmaku, dan); 65 | 66 | let danmaku2 = postprocessPipe.process(danmaku, json); 67 | if (danmaku2) { 68 | list.push(danmaku); 69 | } 70 | } 71 | }); 72 | return list; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/core/Stage/BaseStage.ts: -------------------------------------------------------------------------------- 1 | import { RendererInterface } from "../Renderer/RendererInterface"; 2 | import { Color } from "../Style/Unit/Color"; 3 | import { PositionInterface } from "../Style/PositionInterface"; 4 | import { SizeInterface } from "../Style/SizeInterface"; 5 | import { StageInterface } from "./StageInterface"; 6 | import { PxSize } from "../Style/Unit/PxSize"; 7 | import { TimeLineInterface } from "../../TimeLine/TimeLineInterface"; 8 | import { I18n } from "../../i18n"; 9 | import { TimeLineDanmaku } from "../../TimeLine/TimeLineDanmaku"; 10 | 11 | export abstract class BaseStage implements StageInterface { 12 | protected renderer?: RendererInterface; 13 | protected timeLineObj?: TimeLineInterface; 14 | public stageSize(containersSize: SizeInterface): SizeInterface { 15 | return { 16 | width: containersSize.width, 17 | height: containersSize.height, 18 | }; 19 | } 20 | public stagePosition(): PositionInterface { 21 | return { 22 | x: new PxSize(0), 23 | y: new PxSize(0), 24 | }; 25 | } 26 | public stageBackgroundColor(): Color { 27 | return new Color(0, 0, 0, 0); 28 | } 29 | public stageRenderer(render: RendererInterface): boolean { 30 | this.renderer = render; 31 | return true; 32 | } 33 | public getRenderer(): RendererInterface { 34 | if (this.renderer) { 35 | return this.renderer; 36 | } else { 37 | throw ReferenceError(I18n.t("renderer is null")); 38 | } 39 | } 40 | public timeLine(timeLine: TimeLineInterface): boolean { 41 | this.timeLineObj = timeLine; 42 | return true; 43 | } 44 | public refresh(time: number): boolean { 45 | if (!this.renderer) { 46 | return false; 47 | } 48 | // console.log(time); 49 | 50 | let list: { skip: boolean; DanmakuList: TimeLineDanmaku[] }; 51 | list = this.timeLineObj?.getDanmakuList(time) || { 52 | skip: false, 53 | DanmakuList: [], 54 | }; 55 | if (list) { 56 | if (list.skip) { 57 | this.renderer?.reset(); 58 | } 59 | 60 | list.DanmakuList.forEach((val) => { 61 | this.renderer?.addDanmaku(val.danmaku); 62 | }); 63 | } 64 | this.renderer.refresh(time); 65 | return true; 66 | } 67 | public abstract rendererType(): string; 68 | public abstract attachedType(): string; 69 | public abstract timeLineType(): string; 70 | public reset(): boolean { 71 | this?.renderer?.reset(); 72 | return true; 73 | } 74 | public getTimeLine(): TimeLineInterface { 75 | if (!this.timeLineObj) { 76 | throw ReferenceError(I18n.t("Time line is null")); 77 | } 78 | return this.timeLineObj; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/core/Animation/CompositeAnimation/GroupAnimations.ts: -------------------------------------------------------------------------------- 1 | import { deepMerge } from "../../../util/deepMerge"; 2 | import { AnimationFactory } from "../../../Factory/AnimationFactory"; 3 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 4 | import { AnimationInterface } from "../Base/AnimationInterface"; 5 | import { CubicAnimations } from "../Base/CubicAnimations"; 6 | import { Matrix } from "../Base/Matrix"; 7 | 8 | /** 9 | * 动画组,用于多个动画并行播放 10 | */ 11 | export class GroupAnimations extends CubicAnimations { 12 | public animations: AnimationInterface[] = []; 13 | /** 14 | * 每个动画结束的累计矩阵 15 | */ 16 | public cumulativeMatrix: number[][] = []; 17 | public duration: number = 0; 18 | public setParams(param: { [idx: string]: any }): boolean { 19 | super.setParams(param); 20 | 21 | this.animations = AnimationFactory.getAnimationsList( 22 | param?.animations || [] 23 | ); 24 | // 计算出最大的动画时长 25 | this.animations.forEach((val, key) => { 26 | let dur = val.getDuration(); 27 | // 计算最后一帧 28 | this.cumulativeMatrix[key] = val.getMatrix(dur) || Matrix.getNullMatrix(); 29 | if (dur > this.duration) { 30 | // 这里覆盖了父类的属性 31 | // 实际上,只有父类的时长不够,才会被更新 32 | this.duration = val.getDuration(); 33 | } 34 | }); 35 | 36 | return true; 37 | } 38 | public getCubicStyle(_progress: number, time: number): false | DanmakuStyle { 39 | /** 40 | * 合并样式,由于样式的特殊性,如果存在冲突的样式,后面的会覆盖前面的 41 | */ 42 | let style: DanmakuStyle = { boxStyle: {}, fontStyle: {} }; 43 | let noneStyle = true; 44 | for (const animation of this.animations) { 45 | let style2 = animation.getStyle(time); 46 | 47 | if (style2) { 48 | noneStyle = false; 49 | style = deepMerge(style, style2); 50 | } 51 | } 52 | // for(let i = 0;i= 0; i--) { 70 | let matrix = this.animations[i].getMatrix(time); 71 | // console.log(matrix); 72 | 73 | if (matrix) { 74 | arr = Matrix.mult(arr, matrix); 75 | } else { 76 | arr = Matrix.mult(arr, this.cumulativeMatrix[i]); 77 | } 78 | } 79 | 80 | return arr; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/core/Animation/CompositeAnimation/ListAnimations.ts: -------------------------------------------------------------------------------- 1 | import { AnimationFactory } from "../../../Factory/AnimationFactory"; 2 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 3 | import { AnimationInterface } from "../Base/AnimationInterface"; 4 | import { CubicAnimations } from "../Base/CubicAnimations"; 5 | import { Matrix } from "../Base/Matrix"; 6 | /** 7 | * 动画列表组,所有动画按顺序播放 8 | */ 9 | export class ListAnimations extends CubicAnimations { 10 | public animations: AnimationInterface[] = []; 11 | /** 12 | * 每个动画结束的累计矩阵 13 | */ 14 | public cumulativeMatrix: number[][] = []; 15 | public cumulativeTime: number[] = []; 16 | public duration: number = 0; 17 | public setParams(param: { [idx: string]: any }): boolean { 18 | super.setParams(param); 19 | this.animations = AnimationFactory.getAnimationsList( 20 | param?.animations || [] 21 | ); 22 | 23 | let duration = 0; 24 | this.cumulativeMatrix[0] = Matrix.getNullMatrix(); 25 | this.cumulativeTime[0] = duration; 26 | 27 | this.animations.forEach((val, key) => { 28 | let dur = val.getDuration(); 29 | 30 | // 自增1,因为0的结束帧的1的开头 31 | key++; 32 | // 计算出每个动画结束后的矩阵叠加,传入持续时间以获得最后一帧动画 33 | let matrix = val.getMatrix(dur) || Matrix.getNullMatrix(); 34 | // 叠加 35 | this.cumulativeMatrix[key] = Matrix.mult( 36 | matrix, 37 | this.cumulativeMatrix[key - 1] 38 | ); 39 | 40 | duration += dur; 41 | this.cumulativeTime[key] = duration; 42 | }); 43 | 44 | if (this.duration < duration) { 45 | this.duration = duration; 46 | } 47 | 48 | return true; 49 | } 50 | 51 | public getCubicStyle(_progress: number, time: number): false | DanmakuStyle { 52 | let curAnimation = this.getCurAnimation(time); 53 | 54 | if (curAnimation) { 55 | time = time - this.cumulativeTime[curAnimation.key]; 56 | return curAnimation.animation.getStyle(time); 57 | } 58 | return false; 59 | } 60 | 61 | public getMatrixForCubic(_progress: number, time: number): false | number[] { 62 | let curAnimation = this.getCurAnimation(time); 63 | 64 | if (curAnimation) { 65 | time = time - this.cumulativeTime[curAnimation.key]; 66 | 67 | let mat = 68 | curAnimation.animation.getMatrix(time) || Matrix.getNullMatrix(); 69 | return Matrix.mult(mat, this.cumulativeMatrix[curAnimation.key]); 70 | } 71 | return false; 72 | } 73 | /** 74 | * 获取当前的动画元素,如果不存在则返回false 75 | */ 76 | protected getCurAnimation( 77 | time: number 78 | ): { key: number; animation: AnimationInterface } | false { 79 | for (let i = this.animations.length - 1; i >= 0; i--) { 80 | let duration = this.cumulativeTime[i]; 81 | if (time > duration) { 82 | return { key: i, animation: this.animations[i] }; 83 | } 84 | } 85 | return false; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/core/Renderer/CSS3Renderer/CSS3Renderer.ts: -------------------------------------------------------------------------------- 1 | import { UnitTools } from "../../../util/UnitTools"; 2 | import { DanmakuItemInterface } from "../../Danmaku/DanmakuItemInterface"; 3 | import { CanvasStyle } from "../../Style/CanvasStyle"; 4 | import { SizeInterface } from "../../Style/SizeInterface"; 5 | import { PxSize } from "../../Style/Unit/PxSize"; 6 | import { RendererInterface } from "../RendererInterface"; 7 | import { DanmakuObj, DanmakuTool } from "./CssDanmakuObj"; 8 | 9 | 10 | export class CSS3Renderer implements RendererInterface { 11 | 12 | public canvasSize: SizeInterface = { 13 | width: new PxSize(1920), 14 | height: new PxSize(1080) 15 | } 16 | public canvas?: HTMLElement 17 | public danmakuList: DanmakuObj[] = [] 18 | public setCanvasContainer(canvas: HTMLElement): boolean { 19 | this.canvas = canvas 20 | return true; 21 | } 22 | public updateCanvasStyle(style: CanvasStyle): boolean { 23 | if (this.canvas) { 24 | this.canvas.style.width = this.canvasSize.width.string(); 25 | this.canvas.style.height =this.canvasSize.height.string(); 26 | this.canvas.style.left = UnitTools.lengthSrting((style.size.width.length - this.canvasSize.width.length) / 2) 27 | this.canvas.style.top = UnitTools.lengthSrting((style.size.height.length - this.canvasSize.height.length) / 2) 28 | this.canvas.style.backgroundColor = style.color.string() 29 | let r: number;// 缩放大小 30 | if (style.size.width.length / style.size.height.length >= this.canvasSize.width.length / this.canvasSize.height.length) { 31 | // 横向 32 | r = style.size.height.length / this.canvasSize.height.length 33 | } else { 34 | // 纵向 35 | r = style.size.width.length / this.canvasSize.width.length 36 | } 37 | this.canvas.style.transform = `scale(${r},${r})` 38 | 39 | return true 40 | } else { 41 | return false 42 | } 43 | } 44 | public addDanmaku(danmaku: DanmakuItemInterface): boolean { 45 | if(!this.canvas) { return false } 46 | let danmakuobj = DanmakuTool.recursionDanmaku([danmaku]) 47 | let eles = DanmakuTool.recursionDiv(danmakuobj) 48 | eles.forEach((ele) => { 49 | this.canvas?.appendChild(ele) 50 | }) 51 | this.danmakuList.push(...danmakuobj); 52 | // console.log(danmakuobj); 53 | 54 | return true; 55 | } 56 | 57 | 58 | 59 | public refresh(time): boolean { 60 | if (!this.canvas) { 61 | return false; 62 | } 63 | 64 | DanmakuTool.recursionStyle(this.danmakuList,this.canvas,time) 65 | return true; 66 | } 67 | public reset(): boolean { 68 | // 清空画布 69 | 70 | if(this.canvas){ 71 | this.canvas.innerHTML = ""; 72 | } 73 | // 清空数组 74 | this.danmakuList = [] 75 | return true 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/core/Animation/Base/CubicAnimations.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 2 | import { AnimationInterface } from "./AnimationInterface"; 3 | import { Cubic } from "./Cubic"; 4 | 5 | /** 6 | * 贝塞尔动画类 7 | */ 8 | export abstract class CubicAnimations implements AnimationInterface { 9 | /** 10 | * 贝塞尔函数 11 | */ 12 | public cubicFunc: Cubic; 13 | /** 14 | * 贝塞尔参数 15 | */ 16 | public cubic: [number, number, number, number] = [0, 0, 1, 1]; 17 | /** 18 | * 动画总时间 19 | */ 20 | public duration: number = 1000; 21 | // currentTime:number = 0 22 | public getMatrix(time: number): false | number[] { 23 | if (this.isEnd(time)) { 24 | return false; 25 | } 26 | let p = this.getProgress(time); 27 | let a = this.getMatrixForCubic(p, this.duration * p); 28 | return a; 29 | } 30 | public getStyle(time: number): false | DanmakuStyle { 31 | if (this.isEnd(time)) { 32 | return false; 33 | } 34 | let p = this.getProgress(time); 35 | return this.getCubicStyle(p, this.duration * p); 36 | } 37 | public setParams(param?: { [idx: string]: any }): boolean { 38 | this.cubic = param?.cubic ? param?.cubic : this.cubic; 39 | this.duration = 40 | param?.duration !== undefined ? param?.duration : this.duration; 41 | return true; 42 | } 43 | public getDuration(): number { 44 | return this.duration; 45 | } 46 | /** 47 | * 根据动画时间获取动画进度 48 | * @param time 动画时间 49 | * @returns 50 | */ 51 | public getProgress(time: number): number { 52 | // 不能除0 53 | if (this.duration === 0) { 54 | return 1; 55 | } 56 | // 计算当前动画进度 57 | let progress: number = time / this.duration; 58 | // 计算出实际动画进度 59 | let a = this.getCubic().solve(progress); 60 | return a; 61 | } 62 | /** 63 | * 根据进度返回区间内的值 64 | * @param start 65 | * @param end 66 | * @param progress 67 | */ 68 | public getProgressValue( 69 | start: number, 70 | end: number, 71 | progress: number 72 | ): number { 73 | return start + (end - start) * progress; 74 | } 75 | /** 76 | * 判断动画是否结束 77 | */ 78 | protected isEnd(time: number) { 79 | return time > this.duration; 80 | } 81 | /** 82 | * 获取贝塞尔函数 83 | * @returns 84 | */ 85 | public getCubic(): Cubic { 86 | if (!this.cubicFunc) { 87 | this.cubicFunc = new Cubic(...this.cubic); 88 | } 89 | return this.cubicFunc; 90 | } 91 | /** 92 | * 计算贝塞尔样式 93 | * 注意:此处返回false仅表示未设置动画,请不要在此函数实现中判断动画结束 94 | * @param progress 动画进度,0-1 有可能会超过此范围 95 | */ 96 | public abstract getCubicStyle( 97 | progress: number, 98 | time: number 99 | ): DanmakuStyle | false; 100 | /** 101 | * 计算贝塞尔的矩阵 102 | * 注意:此处返回false仅表示未设置动画,请不要在此函数实现中判断动画结束 103 | * @param progress 动画进度,0-1 有可能会超过此范围 104 | * @ 105 | */ 106 | public abstract getMatrixForCubic( 107 | progress: number, 108 | time: number 109 | ): number[] | false; 110 | } 111 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | 4 | "member-access": true, 5 | "member-ordering": [ 6 | { 7 | "order": [ 8 | "public-static-field", 9 | "public-static-method", 10 | "protected-static-field", 11 | "protected-static-method", 12 | "private-static-field", 13 | "private-static-method", 14 | "public-instance-field", 15 | "protected-instance-field", 16 | "private-instance-field", 17 | "public-constructor", 18 | "protected-constructor", 19 | "private-constructor", 20 | "public-instance-method", 21 | "protected-instance-method", 22 | "private-instance-method" 23 | ] 24 | } 25 | ], 26 | "no-empty-interface": true, 27 | "prefer-for-of": true, 28 | 29 | "await-promise": true, 30 | "curly": true, 31 | "forin": true, 32 | "no-arg": true, 33 | 34 | "no-conditional-assignment": true, 35 | "no-debugger": true, 36 | "no-duplicate-super": true, 37 | "no-empty": true, 38 | "no-eval": true, 39 | "no-for-in-array": true, 40 | "no-invalid-template-strings": true, 41 | "no-invalid-this": true, 42 | "no-null-keyword": true, 43 | "no-sparse-arrays": true, 44 | "no-string-throw": true, 45 | "no-switch-case-fall-through": true, 46 | "no-unsafe-finally": true, 47 | "no-unused-expression": true, 48 | "no-use-before-declare": true, 49 | "no-var-keyword": true, 50 | "radix": true, 51 | "restrict-plus-operands": true, 52 | "use-isnan": true, 53 | "triple-equals": [true, "allow-undefined-check"], 54 | "max-classes-per-file": [ 55 | true, 56 | 1 57 | ], 58 | "no-duplicate-imports": true, 59 | 60 | "align": [ 61 | true, 62 | "parameters", 63 | "arguments", 64 | "statements", 65 | "members", 66 | "elements" 67 | ], 68 | "array-type": [ 69 | true, 70 | "array" 71 | ], 72 | "class-name": true, 73 | "comment-format": [ 74 | true, 75 | "check-space" 76 | ], 77 | "encoding": true, 78 | "import-spacing": true, 79 | "jsdoc-format": true, 80 | "new-parens": true, 81 | "no-trailing-whitespace": [ 82 | true, 83 | "ignore-comments", 84 | "ignore-jsdoc" 85 | ], 86 | "no-unnecessary-initializer": true, 87 | "variable-name": [ 88 | true, 89 | "check-format", 90 | "allow-leading-underscore", 91 | "allow-trailing-underscore", 92 | "ban-keywords" 93 | ] 94 | } 95 | } -------------------------------------------------------------------------------- /src/TimeLine/AdvancedLine.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../core/Danmaku/DanmakuItemInterface"; 2 | import { TimeLineDanmaku } from "./TimeLineDanmaku"; 3 | import { TimeLineInterface } from "./TimeLineInterface"; 4 | 5 | /** 6 | * 高级弹幕时间轴 7 | */ 8 | export class AdvancedLine implements TimeLineInterface { 9 | /** 10 | * 上次获取时间(10ms) 11 | */ 12 | public lastTime: number = 0; 13 | /** 14 | * 时间轴,二维数组,一维是弹幕的开始10毫秒数,二维是此毫秒数下的弹幕集合 15 | * 注意:为了节省性能,最小单位时间是10ms, 16 | * 仅仅是数组下标是10ms,其余的都是按照ms计算的 17 | */ 18 | public danmakuList: TimeLineDanmaku[][] = []; 19 | 20 | /** 21 | * 添加一个弹幕 22 | * @param danmaku 23 | * @param start 24 | * @param end 25 | */ 26 | public addDanmaku( 27 | danmaku: DanmakuItemInterface, 28 | start?: number, 29 | end?: number 30 | ) { 31 | start = start || danmaku.startTime(); 32 | 33 | end = end || danmaku.getAnimation().getDuration() + start; 34 | let index = this.timeToIndex(start); 35 | 36 | if (!this.danmakuList[index]) { 37 | this.danmakuList[index] = []; 38 | } 39 | this.danmakuList[index].push({ 40 | danmaku: danmaku, 41 | start: start, 42 | end: end, 43 | }); 44 | } 45 | /** 46 | * 时间转换 47 | * @param time 48 | * @returns 49 | */ 50 | protected timeToIndex(time: number): number { 51 | return Math.floor(time / 16); 52 | } 53 | public getDanmakuList(time: number): { 54 | skip: boolean; 55 | DanmakuList: TimeLineDanmaku[]; 56 | } { 57 | time = this.timeToIndex(time); 58 | // 如果请求的时间超过了时间轴。。。 59 | if (time < 0) { 60 | return { skip: false, DanmakuList: [] }; 61 | } 62 | 63 | let arr: TimeLineDanmaku[] = []; 64 | let skip = false; 65 | // 当两次时间超过200ms,表示发生了跳转 66 | if (this.lastTime <= time && time - this.lastTime < 5) { 67 | // 正常播放情况,遍历期间的所有内容 68 | // console.log(this.lastTime); 69 | 70 | for (let a = this.lastTime; a < time; a++) { 71 | if (this.danmakuList[a]) { 72 | // console.log(time); 73 | 74 | arr.push(...this.danmakuList[a]); 75 | } 76 | } 77 | } else { 78 | // 否则说明播放发生了跳转,则遍历找到合适的内容 79 | // 查找范围:在当前时间之前开始的,并且结束于当前时间之后的, 80 | // 先找到之前开始的 81 | 82 | for (let timeIndex = 0; timeIndex < time; timeIndex++) { 83 | if (!this.danmakuList[timeIndex]) { 84 | continue; // 当前时间不存在 85 | } 86 | // 接着再查找当前时间之后结束的 87 | for (let l = 0; l < this.danmakuList[timeIndex].length; l++) { 88 | if ( 89 | this.danmakuList[timeIndex][l] && 90 | this.timeToIndex(this.danmakuList[timeIndex][l].end) > time 91 | ) { 92 | arr.push(this.danmakuList[timeIndex][l]); 93 | } 94 | } 95 | } 96 | skip = true; 97 | } 98 | this.lastTime = time; 99 | 100 | return { skip: skip, DanmakuList: arr }; 101 | } 102 | public reset(): boolean { 103 | this.danmakuList = []; 104 | return true; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/core/Renderer/CSS3Renderer/CssDanmakuObj.ts: -------------------------------------------------------------------------------- 1 | import { UnitTools } from "../../../util/UnitTools"; 2 | import { AnimationInterface } from "../../Animation/Base/AnimationInterface"; 3 | import { DanmakuItemInterface } from "../../Danmaku/DanmakuItemInterface"; 4 | import { DanmakuStyle } from "../../Style/DanmakuStyle"; 5 | 6 | export interface DanmakuObj { 7 | element: HTMLElement; 8 | style: DanmakuStyle; 9 | animation: AnimationInterface; 10 | child: DanmakuObj[]; 11 | start: number; 12 | } 13 | export class DanmakuTool { 14 | /** 15 | * 递归构建弹幕对象 16 | * @param danmaku 17 | * @returns 18 | */ 19 | public static recursionDanmaku( 20 | danmaku: DanmakuItemInterface[] 21 | ): DanmakuObj[] { 22 | let danmakuobj: DanmakuObj[] = []; 23 | danmaku.forEach((danmaku) => { 24 | let div = this.createDiv(danmaku.getContent()); 25 | this.setStyle(div, danmaku.getStyle()); 26 | let obj: DanmakuObj = { 27 | element: div, 28 | animation: danmaku.getAnimation(), 29 | style: danmaku.getStyle(), 30 | child: [], 31 | start: danmaku.startTime(), 32 | }; 33 | let child = danmaku.getChild(); 34 | let childObj; 35 | if (child) { 36 | childObj = this.recursionDanmaku(child); 37 | } 38 | 39 | obj.child = childObj; 40 | danmakuobj.push(obj); 41 | }); 42 | return danmakuobj; 43 | } 44 | /** 45 | * 创建一个空div 46 | * @param content 47 | * @returns 48 | */ 49 | public static createDiv(content: string): HTMLElement { 50 | let div = document.createElement("div"); 51 | div.style.position = "absolute"; 52 | div.innerText = content; 53 | return div; 54 | } 55 | 56 | /** 57 | * 递归构建element 58 | * @param danmakuobj 59 | * @returns 60 | */ 61 | public static recursionDiv(danmakuobj: DanmakuObj[]): HTMLElement[] { 62 | let array: HTMLElement[] = []; 63 | danmakuobj.forEach((item) => { 64 | let element = item.element; 65 | if (item.child) { 66 | let eles = this.recursionDiv(item.child); 67 | eles.forEach((ele) => { 68 | element.appendChild(ele); 69 | }); 70 | } 71 | array.push(element); 72 | }); 73 | return array; 74 | } 75 | /** 76 | * 将样式设置到元素中 77 | */ 78 | public static setStyle(element: HTMLElement, style: { [idx: string]: any }) { 79 | Object.assign(element.style, style); 80 | } 81 | /** 82 | * 递归设置弹幕动画 83 | * @param danmaku 84 | */ 85 | public static recursionStyle( 86 | danmaku: DanmakuObj[], 87 | canvas: HTMLElement, 88 | abstime: number, 89 | parentTime: number = 0 90 | ) { 91 | //console.log(danmaku); 92 | danmaku.forEach((dan, key) => { 93 | // 更新弹幕样式 94 | let time = 95 | parentTime === 0 ? abstime - dan.start : parentTime - dan.start; 96 | 97 | // console.log(dan.animation); 98 | 99 | let sty; 100 | let max; 101 | sty = dan.animation.getStyle(time); 102 | // console.log(sty); 103 | if (sty) { 104 | this.setStyle(dan.element, sty); 105 | } 106 | max = dan.animation.getMatrix(time); 107 | if (max) { 108 | dan.element.style.transform = UnitTools.Matrix3dString(max); 109 | } 110 | //console.log(dan); 111 | // 如果都不存在,则表示动画已经完成,销毁元素 112 | // 如果是子元素的情况,有可能存在弹幕未开始的情况 113 | if (!(sty || max)) { 114 | //console.log(dan); 115 | if (dan.element.parentElement) { 116 | // 从父元素删除节点 117 | dan.element.parentElement.removeChild(dan.element); 118 | } else { 119 | // 否则从canvas里面删除 120 | canvas.removeChild(dan.element); 121 | } 122 | // 并且清空节点 123 | delete danmaku[key]; 124 | } 125 | // 递归 126 | this.recursionStyle(dan.child, canvas, abstime, time); 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MFuns 高级弹幕引擎 2 | 此项目已不再活跃,请移至我们的新项目 [mfuns-advanced-danmaku](https://github.com/Mfuns-cn/mfuns-advanced-danmaku) 3 | MF-ADE (MFuns Advanced Danmaku Engine) 高级弹幕引擎 4 | 5 | ## 特性 6 | 7 | 经支持以下弹幕效果: 8 | 9 | - 贝塞尔曲线 10 | - 静止放置 11 | - 3d 移动 12 | - 3d 旋转 13 | - 缩放 14 | - 并行动画 15 | - 串行动画 16 | - 重复动画 17 | - 透明度动画 18 | - 图片弹幕 19 | - 滤镜 20 | - 渐变 21 | - 大部分 CSS 属性 22 | - 模板继承 23 | - mode7+ 弹幕 24 | 更多的效果还在不断的添加和完善中 25 | 26 | 如果使用过程中存在 bug ,欢迎提交 issue 反馈 27 | 28 | 当前版本: `1.8.0 Beta` ,更新日期:`2021/9/12` 29 | 30 | ## 弹幕实验室 31 | 32 | 弹幕实验室目前已经搭建完成 地址: [https://labv2.meogirl.ml](https://labv2.meogirl.ml) 33 | 34 | ## 使用文档 35 | 36 | ### 下载 & 安装 37 | 38 | 下载 lib/mfunsAdvancedDanmaku.js 文件 39 | 使用 script 标记导入到 html 中 40 | 41 | npm 方式: 42 | 43 | 从 1.5.5 版本开始 弹幕引擎已支持 使用 npm 安装 44 | 安装 45 | 46 | ``` 47 | npm install mf-ade --save 48 | ``` 49 | 50 | 导入 51 | 52 | ```js 53 | import { MFADE } from "mf-ade"; 54 | ``` 55 | 56 | ### 基本使用 57 | 58 | 导入弹幕模块,并进行实例化的操作 59 | 60 | ```js 61 | // var danmaku = new MfunsDanMaku({ //从1.4.0版本开始 此名称已被废弃 62 | var danmaku = new MFADE({ 63 | //挂载的容器,要求传入一个HTMLElement对象,详见“容器” 64 | containers: document.getElementById("danmaku"), 65 | //获取弹幕的接口,详见“弹幕接口” 66 | // 1.8.0 之后,以下接口已废弃 67 | // danmaku: (send) => { 68 | // /* 69 | // 获取弹幕,调用回调函数,将弹幕数组传进去 70 | // */ 71 | // send(["json....", "json...."]); 72 | // }, 73 | 74 | // 1.8.0 版 json 代码编辑器弹幕 75 | codeDanmaku: (send) => { 76 | send(["json....", "json...."]); 77 | }, 78 | // 1.8.0 版 mode7+ 弹幕 79 | mode7Danmaku: (send) => { 80 | send(["json....", "json...."]); 81 | }, 82 | }); 83 | ``` 84 | 85 | ### 容器 86 | 87 | containers 容器接受一个 html 元素,但并不会对元素的大小和位置进行设置,所以需要在外部完成对元素进行大小以及位置的设置 88 | 89 | ### 弹幕接口 90 | 91 | 由于弹幕引擎为了更好的可扩展性,内部并无任何网络请求的实现, 92 | 所以需要在这里进行相应的操作,获取弹幕当获取弹幕完成,调用 `send(string[])` 回调函数,由于弹幕可能有很多条,所以回调函数接受一个 json 字符串数组 93 | 94 | 1.8.0 之后新增mode7+ 弹幕接口被拆分成两个 95 | 96 | - `codeDanmaku:(send)=>void`: 原`danmaku:(send)=>void` 接口更名 97 | - `mode7Danmaku:(send)=>void`: mode7+ 弹幕接口 98 | 99 | 100 | ### 弹幕引擎的控制方法 101 | 102 | #### pause() 103 | 104 | 暂停弹幕 105 | 106 | #### start() 107 | 108 | 开始播放 109 | 110 | > 注意:如果获取弹幕进行使用网络进行异步请求,当网络缓慢时,弹幕极有可能没有加载完成,如果此时调用播放,那么弹幕将不会被加载 111 | 112 | #### skip(time:number) 113 | 114 | 跳转到指定位置,该方法要求传入一个单位为毫秒的数字 115 | 116 | #### time():number 117 | 118 | 获取弹幕引擎的内部时间,返回一个单位为毫秒的数字 119 | 120 | #### reset() 121 | 122 | 重置弹幕引擎 123 | 124 | > 该操作会重新调用初始化传入的`danmaku`接口,如果接口中存在网络请求,当网络请求缓慢,可能会发生一些意想不到的 BUG 125 | 126 | > 如果不希望重新获取弹幕,请使用 `skip(0)` 进行跳转 127 | 128 | #### resize() 129 | 130 | 重新计算弹幕容器尺寸 131 | 132 | 一般情况下,浏览器窗口大小变化时,会自动更新尺寸 133 | 134 | 但是也存在一些边缘情况导致舞台尺寸无法正常更新,请调用此函数 135 | 136 | #### listenerEvent(event: DanmakuEventType, callback: (data:any) => void) 137 | 138 | > `1.5.0` 更新 139 | > 监听弹幕事件 140 | 141 | - event 事件类型 142 | 143 | 目前已有的事件类型 144 | 145 | ```js 146 | /** 147 | * 弹幕字符串无效 148 | */ 149 | (DANMAKU_JSON_INVALID = "DANMAKU_JSON_INVALID"), 150 | /** 151 | * 单条弹幕格式错误 152 | */ 153 | (DANMAKU_FORMAT_ERROR = "DANMAKU_FORMAT_ERROR"), 154 | /** 155 | * 弹幕开始加载 156 | */ 157 | (DANMAKU_LOAD_START = "DANMAKU_LOAD_START"), 158 | /** 159 | * 弹幕加载完成 160 | */ 161 | (DANMAKU_LOAD_DONE = "DANMAKU_LOAD_DONE"), 162 | /** 163 | * 弹幕舞台重置 164 | */ 165 | (DANMAKU_STAGE_RESET = "DANMAKU_STAGE_RESET"); 166 | ``` 167 | 168 | - callback 回调函数 169 | 170 | ## mode7+ 接收弹幕格式 171 | 172 | ```json 173 | [ 174 | { 175 | "content":"string",//弹幕内 176 | "start":0,//开始时间 177 | "size":100,//文字大小 178 | "color":"#ffffffff",//文字颜色 179 | "weight":400,//文本粗细 180 | "shadow":true,//阴影 181 | "stroke":1,//描边 182 | "strokeColor":"#fffffff",//描边颜色 183 | "font":"string",//字体 184 | "zIndex":1,//层级, 185 | "linear":false,//线性动画 false时为加速动画 186 | "animations":[//动画关键帧 187 | { 188 | "duration":1000,//动画时间 189 | "x":0,//x 190 | "y":0,//y 191 | "opacity":0,//透明 192 | "rx":0,//旋转 x,y,z 193 | "ry":0, 194 | "rz":0, 195 | "scale":1//缩放 196 | }, 197 | .... 198 | ] 199 | } 200 | ] 201 | ``` 202 | 203 | 204 | 205 | ## 相关链接 206 | 207 | - [弹幕文档](https://meogirl.ml/2021/06/11/danmaku/Mfuns%E5%BC%B9%E5%B9%95%E6%89%8B%E5%86%8C/) 208 | - [弹幕实验室](https://lab.meogirl.ml) [已停止维护] 209 | - [弹幕实验室 v2](https://labv2.meogirl.ml) 210 | 211 | ## 未实现清单 212 | 213 | - 路径动画 offset-path 214 | - svg 属性变换动画 215 | - 内容遮罩 216 | - 滤镜和渐变动画 217 | -------------------------------------------------------------------------------- /src/Factory/DanmakuParser/mode7Danmaku/Mode7DanmakuParser.ts: -------------------------------------------------------------------------------- 1 | import { DanmakuItemInterface } from "../../../core/Danmaku/DanmakuItemInterface"; 2 | import { DanmakuEvent } from "../../../Event/DanmakuEvent"; 3 | import { DanmakuEventType } from "../../../Event/DanmakuEventType"; 4 | import { AnimationFactory } from "../../../Factory/AnimationFactory"; 5 | import { I18n } from "../../../i18n"; 6 | import { DanmakuFactory } from "../DanmakuFactory"; 7 | import { DanmakuParserInterface } from "../DanmakuParserInterface"; 8 | 9 | export class Mode7DanmakuParser implements DanmakuParserInterface { 10 | /** 11 | * m7 弹幕解析器 12 | * 弹幕格式示例 13 | * [ 14 | * { 15 | * content:string,//弹幕内容 16 | * start:number//开始时间 17 | * size:number,//文字大小 18 | * color:"#ffffffff"//文字颜色 19 | * weight:400,//文本粗细 20 | * shadow:true,//阴影 21 | * stroke:number,//描边 22 | * strokeColor:"#fffffff",//描边颜色 23 | * font:string,//字体 24 | * zIndex:1,//层级, 25 | * linear:bool,//线性动画 26 | * animations:[//动画属性 27 | * { 28 | * duration:number//动画时间 29 | * x:number,//x 30 | * y:number,//y 31 | * opacity:number//透明度 32 | * rx:number,//旋转 33 | * ry:number, 34 | * rz:number, 35 | * scale:number//缩放 36 | * }, 37 | * .... 38 | * ] 39 | * } 40 | * ] 41 | * @param content 42 | */ 43 | 44 | public parser(content: string): DanmakuItemInterface[] { 45 | try { 46 | let json: any[] = JSON.parse(content); 47 | return this.getDanmaku(json); 48 | } catch (e) { 49 | // 触发弹幕格式无效事件 50 | DanmakuEvent.dispatch( 51 | DanmakuEventType.DANMAKU_JSON_INVALID, 52 | I18n.t("Danmaku parser fail") 53 | ); 54 | console.warn(I18n.t("Danmaku parser fail")); 55 | console.warn(e); 56 | return []; 57 | } 58 | } 59 | 60 | protected getDanmaku(list: any[]): DanmakuItemInterface[] { 61 | let danmakulist: DanmakuItemInterface[] = []; 62 | list.forEach((danmaku) => { 63 | let danmakuObj = DanmakuFactory.getDanmakuInstance("text"); 64 | let cubic = danmaku.linear ? [0, 0, 1, 1] : [0.42, 0, 1, 1]; 65 | danmakuObj.setParams({ 66 | content: danmaku.content, 67 | start: danmaku.start || 0, 68 | style: this.getStyle(danmaku), 69 | animation: AnimationFactory.getAnimations("list", { 70 | cubic, 71 | animations: this.getAnimations(danmaku.animations || []), 72 | }), // 将列表组成一个list, 73 | }); 74 | danmakulist.push(danmakuObj); 75 | }); 76 | return danmakulist; 77 | } 78 | protected getStyle(danmaku: any) { 79 | return { 80 | fontSize: `${danmaku.size || 40}px`, 81 | color: danmaku.color || "#000", 82 | fontWeight: danmaku.weight || 400, 83 | textShadow: danmaku.shadow ? "5px 5px 5px rgba(0,0,0,0.4)" : "", 84 | "-webkit-text-stroke": `${danmaku.stroke || 0}px ${ 85 | danmaku.strokeColor || "#000" 86 | }`, 87 | fontFamily: danmaku.font || "黑体", 88 | zIndex: danmaku.zIndex || 1, 89 | }; 90 | } 91 | /** 92 | * 封装动画 93 | * { 94 | * duration:number//动画时间 95 | * x:number,//x 96 | * y:number,//y 97 | * opacity:number//透明度 98 | * rx:number,//旋转 99 | * ry:number, 100 | * rz:number, 101 | * scale:number//缩放 102 | * } 103 | * @param animations 104 | */ 105 | protected getAnimations(animations: any[]) { 106 | let animationsList: any[] = []; 107 | let lastAnimation; 108 | 109 | if (!Array.isArray(animations) || animations === []) { 110 | return animationsList; 111 | } 112 | animations.forEach((animation) => { 113 | let { 114 | x: x2 = 0, 115 | y: y2 = 0, 116 | opacity: o2 = 1, 117 | rx: rx2 = 0, 118 | ry: ry2 = 0, 119 | rz: rz2 = 0, 120 | scale: s2 = 1, 121 | } = animation; 122 | if (s2 === 0) { 123 | s2 = 1; 124 | } 125 | let danmakuObj: { type: string; animations: any[] } = { 126 | type: "group", 127 | animations: [], 128 | }; 129 | if (!lastAnimation) { 130 | // 第一次,添加初始位置 131 | 132 | // xy 移动 133 | danmakuObj.animations.push({ 134 | type: "static", 135 | duration: 0, 136 | x: x2, 137 | y: y2, 138 | }); 139 | // 旋转 140 | danmakuObj.animations.push({ 141 | type: "rotateX", 142 | duration: 0, 143 | angle: { 144 | end: rx2, 145 | }, 146 | }); 147 | danmakuObj.animations.push({ 148 | type: "rotateY", 149 | duration: 0, 150 | angle: { 151 | end: ry2, 152 | }, 153 | }); 154 | danmakuObj.animations.push({ 155 | type: "rotateZ", 156 | duration: 0, 157 | angle: { 158 | end: rz2, 159 | }, 160 | }); 161 | // 缩放 162 | danmakuObj.animations.push({ 163 | type: "scale", 164 | duration: 0, 165 | scale: { 166 | x2: s2, 167 | y2: s2, 168 | z2: s2, 169 | }, 170 | }); 171 | } else { 172 | let { 173 | x: x1 = 0, 174 | y: y1 = 0, 175 | duration = 1000, 176 | opacity: o1 = 1, 177 | rx: rx1 = 0, 178 | ry: ry1 = 0, 179 | rz: rz1 = 0, 180 | } = lastAnimation; 181 | 182 | // 移动 183 | danmakuObj.animations.push({ 184 | type: "translate", 185 | duration, 186 | path: { 187 | x2: x2 - x1, 188 | y2: y2 - y1, 189 | }, 190 | }); 191 | // 透明度 192 | danmakuObj.animations.push({ 193 | type: "opacity", 194 | duration, 195 | opacity: [o1, o2], 196 | }); 197 | // 旋转 198 | danmakuObj.animations.push({ 199 | type: "rotateX", 200 | duration, 201 | angle: { 202 | end: rx2 - rx1, 203 | }, 204 | }); 205 | danmakuObj.animations.push({ 206 | type: "rotateY", 207 | duration, 208 | angle: { 209 | end: ry2 - ry1, 210 | }, 211 | }); 212 | danmakuObj.animations.push({ 213 | type: "rotateZ", 214 | duration, 215 | angle: { 216 | end: rz2 - rz1, 217 | }, 218 | }); 219 | // 缩放 220 | danmakuObj.animations.push({ 221 | type: "scale", 222 | duration, 223 | scale: { 224 | x2: s2, 225 | y2: s2, 226 | z2: s2, 227 | }, 228 | }); 229 | console.log(s2); 230 | } 231 | 232 | animationsList.push(danmakuObj); 233 | lastAnimation = animation; 234 | }); 235 | let { duration = 1000 } = lastAnimation; 236 | animationsList.push({ 237 | type: "static", 238 | duration, 239 | }); 240 | // console.log(animationsList); 241 | return animationsList; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/Controller/Controller.ts: -------------------------------------------------------------------------------- 1 | import { SizeInterface } from "../core/Style/SizeInterface"; 2 | import { StageInterface } from "../core/Stage/StageInterface"; 3 | import { Context } from "../Context/Context"; 4 | import { I18n } from "../i18n"; 5 | import { CanvasStyle } from "../core/Style/CanvasStyle"; 6 | import { PxSize } from "../core/Style/Unit/PxSize"; 7 | import { TimeLineFactory } from "../Factory/TimeLineFactory"; 8 | import { DanmakuEvent } from "../Event/DanmakuEvent"; 9 | import { DanmakuEventType } from "../Event/DanmakuEventType"; 10 | import { RendererFactory } from "../Factory/RendererFactory"; 11 | import { DanmakuParserFactory } from "../Factory/DanmakuParser/DanmakuParserFactory"; 12 | 13 | /** 14 | * 控制器 ,统一管理整个弹幕系统 15 | * 在这里,每种弹幕类型都会得到一个舞台对象,进行渲染 16 | */ 17 | export class Controller { 18 | /** 19 | * 画布对象 20 | */ 21 | protected containers: HTMLElement; 22 | /** 23 | * 实时的Css样式表 24 | */ 25 | protected canvasStyle: CSSStyleDeclaration; 26 | /** 27 | * 舞台列表 28 | */ 29 | protected stageList: StageInterface[] = []; 30 | /** 31 | * 暂停状态 32 | */ 33 | protected pauseStatus: boolean = true; 34 | /** 35 | * 时间戳 36 | */ 37 | protected timeStamp: number = 0; 38 | /** 39 | * 播放的时间 40 | */ 41 | protected time: number = 0; 42 | /** 43 | * 跳转状态 44 | */ 45 | public skipStatus: boolean = false; 46 | 47 | /** 48 | * 弹幕获取器函数列表 49 | */ 50 | protected danmakuFunction: { 51 | [type: string]: (send: (str: string[]) => void) => void; 52 | } = {}; 53 | 54 | constructor(containers: HTMLElement) { 55 | this.containers = containers; 56 | // 获取实时的style对象,当大小发生变化时,会更新自身 57 | this.canvasStyle = window.getComputedStyle(containers); 58 | // 初始化容器 59 | this.initContainer(); 60 | let that = this; 61 | (function animloop() { 62 | if (!that.pauseStatus) { 63 | that.refresh(); 64 | } 65 | requestAnimationFrame(animloop); 66 | })(); 67 | } 68 | /** 69 | * 获得容器尺寸 70 | */ 71 | public getContainersSize(): SizeInterface { 72 | return { 73 | width: new PxSize(parseInt(this.canvasStyle.width, 10)), 74 | height: new PxSize(parseInt(this.canvasStyle.height, 10)), 75 | }; 76 | } 77 | /** 78 | * 将一个舞台添加到容器内 79 | * @param stage 舞台 80 | * @param index 排列顺序 81 | * @returns 成功与失败的状态 82 | */ 83 | public registStage(stage: StageInterface, index: number): boolean { 84 | // 检查列表中是否存在 85 | if (this.stageList[index]) { 86 | return false; 87 | } 88 | this.stageList[index] = stage; 89 | return true; 90 | } 91 | /** 92 | * 将舞台挂载到容器中 93 | */ 94 | public mount() { 95 | // console.info(I18n.t("Start mount stage")); 96 | // 遍历每一个舞台 97 | this.stageList.forEach((stage, key) => { 98 | // 获取一个div 99 | let div = this.getDIV(); 100 | // 给舞台初始化渲染器 101 | let render = RendererFactory.getRenderInstance(stage.rendererType()); 102 | // 将div挂载到渲染器 103 | render.setCanvasContainer(div); 104 | // 设置舞台渲染器 105 | stage.stageRenderer(render); 106 | // 设置舞台时间轴 107 | let lineType = stage.timeLineType(); 108 | let timeline = TimeLineFactory.getTimeLine(lineType); 109 | stage.timeLine(timeline); 110 | // 检察是否存在弹幕获取器 111 | let attachedType = stage.attachedType(); 112 | if (this.danmakuFunction[attachedType]) { 113 | // 如果存在,就获取弹幕 114 | this.resetDanmaku(key); 115 | } else { 116 | console.warn(I18n.t("danmaku get function is null :" + attachedType)); 117 | } 118 | 119 | // 更新渲染器内画布样式 120 | render.updateCanvasStyle(this.getCanvasStylByStage(stage)); 121 | }); 122 | } 123 | 124 | /** 125 | * 初始化弹幕容器 126 | */ 127 | protected initContainer() { 128 | if (Context.debug) { 129 | // debug模式 130 | this.containers.classList.add("danmaku-containers-debug"); 131 | } 132 | this.containers.classList.add("danmaku-containers"); 133 | } 134 | 135 | /** 136 | * 重置尺寸 137 | */ 138 | public resize() { 139 | // 重置舞台的尺寸 140 | this.stageList.forEach((stage) => { 141 | let render = stage.getRenderer(); 142 | if (render) { 143 | render.updateCanvasStyle(this.getCanvasStylByStage(stage)); 144 | } 145 | }); 146 | } 147 | 148 | /** 149 | * 创建div容器 150 | */ 151 | protected getDIV(): HTMLElement { 152 | let div = document.createElement("div"); 153 | div.classList.add("stage"); 154 | this.containers.appendChild(div); 155 | return div; 156 | } 157 | 158 | /** 159 | * 根据舞台对象创建一个canvasStyle 160 | * @param stage 舞台对象 161 | * @returns 162 | */ 163 | protected getCanvasStylByStage(stage: StageInterface): CanvasStyle { 164 | let size = stage.stageSize(this.getContainersSize()); 165 | let color = stage.stageBackgroundColor(this.getContainersSize()); 166 | let pos = stage.stagePosition(this.getContainersSize(), size); 167 | return { position: pos, color: color, size: size }; 168 | } 169 | 170 | public refresh() { 171 | this.time = Date.now() - this.timeStamp; 172 | // 通知每个舞台刷新 173 | this.stageList.forEach((stage) => { 174 | // console.log(1) 175 | stage.refresh(this.time); 176 | }); 177 | } 178 | 179 | /** 180 | * 暂停 181 | */ 182 | public pause() { 183 | if (!this.pauseStatus) { 184 | this.pauseStatus = true; 185 | } 186 | } 187 | 188 | /** 189 | * 播放 190 | */ 191 | public start() { 192 | if (this.pauseStatus) { 193 | // 同步时间 194 | this.timeStamp = Date.now() - this.time; 195 | this.pauseStatus = false; 196 | } 197 | } 198 | 199 | /** 200 | * 跳转 201 | */ 202 | public skip(time: number) { 203 | if (this.pauseStatus) { 204 | // 如果是暂停状态 205 | this.time = time; 206 | } else { 207 | // 否则使用这个方法 208 | this.timeStamp = Date.now() - time; 209 | } 210 | } 211 | 212 | /** 213 | * 重置整个系统 214 | */ 215 | public reset() { 216 | // 清空所有舞台 217 | this.stageList.forEach((stage) => { 218 | stage.reset(); 219 | }); 220 | // 触发弹幕舞台重置事件 221 | DanmakuEvent.dispatch(DanmakuEventType.DANMAKU_STAGE_RESET, {}); 222 | 223 | this.pauseStatus = true; 224 | this.timeStamp = 0; 225 | this.time = 0; 226 | this.skipStatus = false; 227 | } 228 | 229 | /** 230 | * 获取弹幕当前时间 231 | * @returns number 232 | */ 233 | public getTime(): number { 234 | return this.time; 235 | } 236 | 237 | /** 238 | * 添加弹幕获取器 239 | * @param type 舞台的附属类型 240 | * @param fun 回调函数 241 | */ 242 | public addGetDanmakuFunction( 243 | type: string, 244 | fun: (send: (str: string[]) => void) => void 245 | ) { 246 | this.danmakuFunction[type] = fun; 247 | } 248 | 249 | /** 250 | * 重置弹幕接口 251 | */ 252 | public resetDanmaku(type: number) { 253 | // 判断舞台列表是否存在 254 | if (!this.stageList[type]) { 255 | return; 256 | } 257 | 258 | // 弹幕开始加载事件 259 | DanmakuEvent.dispatch(DanmakuEventType.DANMAKU_LOAD_START, {}); 260 | // 获取当前舞台 261 | let stage = this.stageList[type]; 262 | // 获取弹幕时间轴 263 | let timeline = stage.getTimeLine(); 264 | // 重置时间轴 265 | timeline.reset(); 266 | // 根据时间轴类型找到对应的弹幕获取器 267 | let danmakuGetter = this.danmakuFunction[stage.attachedType()]; 268 | // 判断弹幕获取器是否存在 269 | if (!danmakuGetter) { 270 | return; 271 | } 272 | danmakuGetter((res: string[]) => { 273 | // 解析弹幕文本 274 | res.forEach((danmakuStr: string) => { 275 | let parser = DanmakuParserFactory.getInstance(stage.attachedType()); 276 | // let parser = new CodeDanmakuParser(); 277 | // 遍历添加进时间轴 278 | parser.parser(danmakuStr).forEach((danmaku) => { 279 | console.log(danmaku.getAnimation()); 280 | timeline.addDanmaku(danmaku); 281 | }); 282 | // 弹幕加载完成事件 283 | DanmakuEvent.dispatch(DanmakuEventType.DANMAKU_LOAD_DONE, {}); 284 | }); 285 | }); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /lib/mfunsAdvancedDanmaku.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var t={705:t=>{t.exports=function(t){var n=[];return n.toString=function(){return this.map((function(n){var e=t(n);return n[2]?"@media ".concat(n[2]," {").concat(e,"}"):e})).join("")},n.i=function(t,e,r){"string"==typeof t&&(t=[[null,t,""]]);var i={};if(r)for(var o=0;o{function n(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=new Array(n);e{e.d(n,{Z:()=>s});var r=e(880),i=e.n(r),o=e(705),a=e.n(o)()(i());a.push([t.id,'.danmaku-containers {\r\n position: relative;\r\n}\r\n.danmaku-containers * {\r\n box-sizing: border-box;\r\n transform-style: preserve-3d;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 40px;\r\n image-rendering: pixelated; /* 小图片放大后像素不虚化 */\r\n image-rendering: -moz-crisp-edges;\r\n line-height: 1;\r\n font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif;\r\n color: #000;\r\n border: none;\r\n background: none;\r\n}\r\n.danmaku-containers .stage {\r\n position: absolute;\r\n overflow: hidden;\r\n perspective: 1000px;\r\n transform-style: preserve-3d;\r\n}\r\n.danmaku-containers-debug * {\r\n outline: 1px solid red;\r\n}\r\n',"",{version:3,sources:["webpack://./css/base.css"],names:[],mappings:"AAAA;EACE,kBAAkB;AACpB;AACA;EACE,sBAAsB;EACtB,4BAA4B;EAC5B,SAAS;EACT,UAAU;EACV,eAAe;EACf,0BAA0B,EAAE,gBAAgB;EAC5C,iCAAiC;EACjC,cAAc;EACd,oEAAoE;EACpE,WAAW;EACX,YAAY;EACZ,gBAAgB;AAClB;AACA;EACE,kBAAkB;EAClB,gBAAgB;EAChB,mBAAmB;EACnB,4BAA4B;AAC9B;AACA;EACE,sBAAsB;AACxB",sourcesContent:['.danmaku-containers {\r\n position: relative;\r\n}\r\n.danmaku-containers * {\r\n box-sizing: border-box;\r\n transform-style: preserve-3d;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 40px;\r\n image-rendering: pixelated; /* 小图片放大后像素不虚化 */\r\n image-rendering: -moz-crisp-edges;\r\n line-height: 1;\r\n font-family: "Helvetica Neue", Helvetica, STHeiTi, Arial, sans-serif;\r\n color: #000;\r\n border: none;\r\n background: none;\r\n}\r\n.danmaku-containers .stage {\r\n position: absolute;\r\n overflow: hidden;\r\n perspective: 1000px;\r\n transform-style: preserve-3d;\r\n}\r\n.danmaku-containers-debug * {\r\n outline: 1px solid red;\r\n}\r\n'],sourceRoot:""}]);const s=a},379:(t,n,e)=>{var r,i=function(){var t={};return function(n){if(void 0===t[n]){var e=document.querySelector(n);if(window.HTMLIFrameElement&&e instanceof window.HTMLIFrameElement)try{e=e.contentDocument.head}catch(t){e=null}t[n]=e}return t[n]}}(),o=[];function a(t){for(var n=-1,e=0;e{var n=t&&t.__esModule?()=>t.default:()=>t;return e.d(n,{a:n}),n},e.d=(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},e.o=(t,n)=>Object.prototype.hasOwnProperty.call(t,n),(()=>{var t=e(379),n=e.n(t),r=e(628);n()(r.Z,{insert:"head",singleton:!1}),r.Z.locals;var i=function(){function t(){}return t.language="zh-cn",t.debug=!0,t}();const o={"Containers is null":"容器为空","Start mount stage":"开始挂载舞台","Renderer type is null":"没有找到合适的渲染器","matrix3d param less than 16":"matrix3d 参数小于16个","Unknown danmaku format":"未知的弹幕格式","Danmaku parser fail":"高级弹幕解析失败,json格式可能存在问题"};var a,s=function(){function t(){}return t.t=function(t){return this.tranTxt[this.language]&&this.tranTxt[this.language][t]?this.tranTxt[this.language][t]:t},t.tranTxt={"zh-cn":o},t.language=i.language,t}(),u=function(){function t(t){this.length=t}return t.prototype.string=function(){return this.length+"px"},t}(),c=function(){function t(){this.lastTime=0,this.danmakuList=[]}return t.prototype.addDanmaku=function(t,n,e){n=n||t.startTime(),e=e||t.getAnimation().getDuration()+n;var r=this.timeToIndex(n);this.danmakuList[r]||(this.danmakuList[r]=[]),this.danmakuList[r].push({danmaku:t,start:n,end:e})},t.prototype.timeToIndex=function(t){return Math.floor(t/16)},t.prototype.getDanmakuList=function(t){if((t=this.timeToIndex(t))<0)return{skip:!1,DanmakuList:[]};var n=[],e=!1;if(this.lastTime<=t&&t-this.lastTime<5)for(var r=this.lastTime;rt&&n.push(this.danmakuList[i][o]);e=!0}return this.lastTime=t,{skip:e,DanmakuList:n}},t.prototype.reset=function(){return this.danmakuList=[],!0},t}(),l=function(){function t(){}return t.getTimeLine=function(t){return this.list[t]||(t="default"),new this.list[t]},t.list={default:c,advanced:c},t}(),p=function(){function t(){}return t.buildPrefix=function(t){return this.prefix+t},t.listener=function(t,n){window.addEventListener(this.buildPrefix(t),(function(t){n(t.detail)}))},t.dispatch=function(t,n){var e=new CustomEvent(this.buildPrefix(t),{detail:n});window.dispatchEvent(e)},t.prefix="_danmakuevent_",t}();!function(t){t.DANMAKU_JSON_INVALID="DANMAKU_JSON_INVALID",t.DANMAKU_FORMAT_ERROR="DANMAKU_FORMAT_ERROR",t.DANMAKU_LOAD_START="DANMAKU_LOAD_START",t.DANMAKU_LOAD_DONE="DANMAKU_LOAD_DONE",t.DANMAKU_STAGE_RESET="DANMAKU_STAGE_RESET"}(a||(a={}));var h=function(){function t(){}return t.prototype.reset=function(){throw new Error("Method not implemented.")},t.prototype.addDanmaku=function(t){return!1},t.prototype.refresh=function(){return!1},t.prototype.setCanvasContainer=function(t){return this.canvas=t,!0},t.prototype.updateCanvasStyle=function(t){return!!this.canvas&&(this.canvas.style.width=t.size.width.string(),this.canvas.style.height=t.size.height.string(),this.canvas.style.left=t.position.x.string(),this.canvas.style.top=t.position.y.string(),this.canvas.style.backgroundColor=t.color.string(),!0)},t}(),f=function(){function t(){}return t.lengthSrting=function(t){return t+"px"},t.Matrix3dString=function(t){if(16!==t.length)throw SyntaxError(s.t("matrix3d param less than 16"));return"Matrix3d("+t.join(",")+")"},t}(),d=function(){function t(){}return t.recursionDanmaku=function(t){var n=this,e=[];return t.forEach((function(t){var r=n.createDiv(t.getContent());n.setStyle(r,t.getStyle());var i,o={element:r,animation:t.getAnimation(),style:t.getStyle(),child:[],start:t.startTime()},a=t.getChild();a&&(i=n.recursionDanmaku(a)),o.child=i,e.push(o)})),e},t.createDiv=function(t){var n=document.createElement("div");return n.style.position="absolute",n.innerText=t,n},t.recursionDiv=function(t){var n=this,e=[];return t.forEach((function(t){var r=t.element;t.child&&n.recursionDiv(t.child).forEach((function(t){r.appendChild(t)})),e.push(r)})),e},t.setStyle=function(t,n){Object.assign(t.style,n)},t.recursionStyle=function(t,n,e,r){var i=this;void 0===r&&(r=0),t.forEach((function(o,a){var s,u,c=0===r?e-o.start:r-o.start;(s=o.animation.getStyle(c))&&i.setStyle(o.element,s),(u=o.animation.getMatrix(c))&&(o.element.style.transform=f.Matrix3dString(u)),s||u||(o.element.parentElement?o.element.parentElement.removeChild(o.element):n.removeChild(o.element),delete t[a]),i.recursionStyle(o.child,n,e,c)}))},t}(),y=function(){function t(){this.canvasSize={width:new u(1920),height:new u(1080)},this.danmakuList=[]}return t.prototype.setCanvasContainer=function(t){return this.canvas=t,!0},t.prototype.updateCanvasStyle=function(t){if(this.canvas){this.canvas.style.width=this.canvasSize.width.string(),this.canvas.style.height=this.canvasSize.height.string(),this.canvas.style.left=f.lengthSrting((t.size.width.length-this.canvasSize.width.length)/2),this.canvas.style.top=f.lengthSrting((t.size.height.length-this.canvasSize.height.length)/2),this.canvas.style.backgroundColor=t.color.string();var n;return n=t.size.width.length/t.size.height.length>=this.canvasSize.width.length/this.canvasSize.height.length?t.size.height.length/this.canvasSize.height.length:t.size.width.length/this.canvasSize.width.length,this.canvas.style.transform="scale("+n+","+n+")",!0}return!1},t.prototype.addDanmaku=function(t){var n,e=this;if(!this.canvas)return!1;var r=d.recursionDanmaku([t]);return d.recursionDiv(r).forEach((function(t){var n;null===(n=e.canvas)||void 0===n||n.appendChild(t)})),(n=this.danmakuList).push.apply(n,r),!0},t.prototype.refresh=function(t){return!!this.canvas&&(d.recursionStyle(this.danmakuList,this.canvas,t),!0)},t.prototype.reset=function(){return this.canvas&&(this.canvas.innerHTML=""),this.danmakuList=[],!0},t}(),g=function(){function t(){}return t.getRenderInstance=function(t){if(this.rendererList[t])return new this.rendererList[t];throw ReferenceError(s.t("Renderer type is null"))},t.rendererList={base:h,css3:y},t}();function v(t,n){var e;for(e in n)t[e]=t[e]&&"[object Object]"===t[e].toString()&&n[e]&&"[object Object]"===n[e].toString()?v(t[e],n[e]):t[e]=n[e];return t}var m,A=function(){function t(t,n,e,r){this.px3=3*t,this.px2=3*(e-t)-this.px3,this.px1=1-this.px3-this.px2,this.py3=3*n,this.py2=3*(r-n)-this.py3,this.py1=1-this.py3-this.py2,this.epsilon=1e-7}return t.prototype.getX=function(t){return((this.px1*t+this.px2)*t+this.px3)*t},t.prototype.getY=function(t){return((this.py1*t+this.py2)*t+this.py3)*t},t.prototype.solve=function(t){if(0===t||1===t)return this.getY(t);for(var n=t,e=0;e<8;e++){var r=this.getX(n)-t;if(Math.abs(r)this.duration},t.prototype.getCubic=function(){return this.cubicFunc||(this.cubicFunc=new(A.bind.apply(A,function(t,n,e){if(e||2===arguments.length)for(var r,i=0,o=n.length;ie.duration&&(e.duration=t.getDuration())})),!0},n.prototype.getCubicStyle=function(t,n){for(var e={boxStyle:{},fontStyle:{}},r=!0,i=0,o=this.animations;i=0;r--){var i=this.animations[r].getMatrix(n);e=i?x.mult(e,i):x.mult(e,this.cumulativeMatrix[r])}return e},n}(b),C=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),O=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.animations=[],n.cumulativeMatrix=[],n.cumulativeTime=[],n.duration=0,n}return C(n,t),n.prototype.setParams=function(n){var e=this;t.prototype.setParams.call(this,n),this.animations=Y.getAnimationsList((null==n?void 0:n.animations)||[]);var r=0;return this.cumulativeMatrix[0]=x.getNullMatrix(),this.cumulativeTime[0]=r,this.animations.forEach((function(t,n){var i=t.getDuration();n++;var o=t.getMatrix(i)||x.getNullMatrix();e.cumulativeMatrix[n]=x.mult(o,e.cumulativeMatrix[n-1]),r+=i,e.cumulativeTime[n]=r})),this.duration=0;n--)if(t>this.cumulativeTime[n])return{key:n,animation:this.animations[n]};return!1},n}(b),k=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),E=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.opacity=[0,1],n}return k(n,t),n.prototype.setParams=function(n){return t.prototype.setParams.call(this,n),this.opacity=(null==n?void 0:n.opacity)||this.opacity,!0},n.prototype.getCubicStyle=function(t,n){return{opacity:this.getProgressValue(this.opacity[0],this.opacity[1],t)}},n.prototype.getMatrixForCubic=function(t,n){return!1},n}(b),T=function(){function t(){this.duration=1e3,this.x=0,this.y=0,this.z=0}return t.prototype.getDuration=function(){return this.duration},t.prototype.setParams=function(t){return this.duration=void 0!==(null==t?void 0:t.duration)?null==t?void 0:t.duration:this.duration,this.x=(null==t?void 0:t.x)||0,this.y=(null==t?void 0:t.y)||0,this.z=(null==t?void 0:t.z)||0,!0},t.prototype.getMatrix=function(t){return!(t>this.duration)&&[1,0,0,0,0,1,0,0,0,0,1,0,this.x,this.y,this.z,1]},t.prototype.getStyle=function(){return!1},t}(),D=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),L=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.repeat=1,n.duration=0,n}return D(n,t),n.prototype.getCubicStyle=function(t,n){return this.animation.getStyle(this.getCurTime(n))},n.prototype.getMatrixForCubic=function(t,n){return this.animation.getMatrix(this.getCurTime(n))},n.prototype.setParams=function(n){if(t.prototype.setParams.call(this,n),null==n?void 0:n.animations){var e=Y.getAnimations("list",{animations:null==n?void 0:n.animations});this.animation=e||new T}else this.animation=new T;return this.repeat=(null==n?void 0:n.repeat)||this.repeat,this.animationTime=this.animation.getDuration(),this.duration=this.animationTime*this.repeat,!0},n.prototype.getCurTime=function(t){return t%this.animationTime},n}(b),M=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),P=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.origin=[0,0,0],n}return M(n,t),n.prototype.setParams=function(n){return!!t.prototype.setParams.call(this,n)&&(this.origin=(null==n?void 0:n.origin)||this.origin,!0)},n.prototype.getMatrixForCubic=function(t){var n=this.getMatrixForOrigin(t);return!!n&&(n=x.mult([1,0,0,0,0,1,0,0,0,0,1,0,-this.origin[0],-this.origin[1],-this.origin[2],1],n),x.mult(n,[1,0,0,0,0,1,0,0,0,0,1,0,this.origin[0],this.origin[1],this.origin[2],1]))},n}(b),j=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),z=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.scale={x1:1,y1:1,z1:1,x2:1,y2:1,z2:1},n}return j(n,t),n.prototype.setParams=function(n){return!!t.prototype.setParams.call(this,n)&&(this.scale=Object.assign(this.scale,null==n?void 0:n.scale),!0)},n.prototype.getMatrixForOrigin=function(t){return[this.getProgressValue(this.scale.x1,this.scale.x2,t),0,0,0,0,this.getProgressValue(this.scale.y1,this.scale.y2,t),0,0,0,0,this.getProgressValue(this.scale.z1,this.scale.z2,t),0,0,0,0,1]},n.prototype.getCubicStyle=function(t){return!1},n}(P),N=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),R=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.path={x1:0,y1:0,x2:0,y2:0,z1:0,z2:0},n}return N(n,t),n.prototype.setParams=function(n){return!!t.prototype.setParams.call(this,n)&&(this.path=Object.assign(this.path,null==n?void 0:n.path),!0)},n.prototype.getCubicStyle=function(){return!1},n.prototype.getMatrixForCubic=function(t){return[1,0,0,0,0,1,0,0,0,0,1,0,this.getProgressValue(this.path.x1,this.path.x2,t),this.getProgressValue(this.path.y1,this.path.y2,t),this.getProgressValue(this.path.z1,this.path.z2,t),1]},n}(b),B=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),I=function(t){function n(){var n=null!==t&&t.apply(this,arguments)||this;return n.angle={start:0,end:360},n}return B(n,t),n.prototype.setParams=function(n){return!!t.prototype.setParams.call(this,n)&&(this.angle=Object.assign(this.angle,null==n?void 0:n.angle),!0)},n.prototype.getCubicStyle=function(t){return!1},n.prototype.A2R=function(t){return t*(Math.PI/180)},n}(P),F=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),U=function(t){function n(){return null!==t&&t.apply(this,arguments)||this}return F(n,t),n.prototype.getMatrixForOrigin=function(t){var n=this.getProgressValue(this.angle.start,this.angle.end,t),e=Math.sin(this.A2R(n)),r=Math.cos(this.A2R(n));return[1,0,0,0,0,r,e,0,0,-e,r,0,0,0,0,1]},n}(I),V=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),K=function(t){function n(){return null!==t&&t.apply(this,arguments)||this}return V(n,t),n.prototype.getMatrixForOrigin=function(t){var n=this.getProgressValue(this.angle.start,this.angle.end,t),e=Math.sin(this.A2R(n)),r=Math.cos(this.A2R(n));return[r,0,e,0,0,1,0,0,-e,0,r,0,0,0,0,1]},n}(I),J=function(){var t=function(n,e){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,n){t.__proto__=n}||function(t,n){for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])})(n,e)};return function(n,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=n}t(n,e),n.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}}(),H=function(t){function n(){return null!==t&&t.apply(this,arguments)||this}return J(n,t),n.prototype.getMatrixForOrigin=function(t){var n=this.getProgressValue(this.angle.start,this.angle.end,t),e=Math.sin(this.A2R(n)),r=Math.cos(this.A2R(n));return[r,e,0,0,-e,r,0,0,0,0,1,0,0,0,0,1]},n}(I),Y=function(){function t(){}return t.getAnimations=function(t,n){if(this.animationList[t]){var e=new this.animationList[t];return e.setParams(n),e}return!1},t.getAnimationsList=function(t){var n=this,e=[];return null==t||t.forEach((function(t){var r=n.getAnimations(t.type,t);r&&e.push(r)})),e},t.animationList={static:T,translate:R,rotateX:U,rotateY:K,rotateZ:H,scale:z,list:O,group:S,repeat:L,opacity:E},t}(),Z=function(){function t(){this.style={},this.start=0,this.content="",this.child=[]}return t.prototype.setParams=function(t){try{(null==t?void 0:t.start)&&(this.start=null==t?void 0:t.start),(null==t?void 0:t.animation)&&(this.animation=(null==t?void 0:t.animation)||new T),(null==t?void 0:t.child)&&(this.child=t.child),(null==t?void 0:t.style)&&(this.style=null==t?void 0:t.style),(null==t?void 0:t.content)&&(this.content=null==t?void 0:t.content)}catch(t){return console.warn(t),!1}return!0},t.prototype.getType=function(){return"base"},t.prototype.getContent=function(){return this.content},t.prototype.getChild=function(){return this.child},t.prototype.getAnimation=function(){return this.animation||(this.animation=new T),this.animation},t.prototype.getStyle=function(){return this.style},t.prototype.startTime=function(){return this.start},t}(),G=function(){function t(){}return t.getDanmakuInstance=function(t){return this.list[t]||(t="text"),new this.list[t]},t.list={text:Z},t}(),X=function(){function t(){}return t.prototype.process=function(t,n){return(null==t?void 0:t.animations)&&(null==t?void 0:t.animations)!==[]||(t.animations=[{type:"static"}]),t},t}(),W=function(){function t(){this.idList=[]}return t.prototype.process=function(t,n){var e=t;if(null==e?void 0:e.extends){if((null==e?void 0:e.id)===(null==e?void 0:e.extends))return t;var r=this.idList[t.extends];r&&(e=v(this.copy(r),t))}return(null==t?void 0:t.id)&&this.save(t.id,e),this.save("_LAST_",e),e},t.prototype.copy=function(t){return JSON.parse(JSON.stringify(t))},t.prototype.save=function(t,n){var e=this.copy(n);delete e.id,this.idList[t]=e},t}(),q=function(){function t(){}return t.prototype.process=function(t,n){if(!(null==t?void 0:t.animations)||(null==t?void 0:t.animations)===[]){var e=(null==t?void 0:t.x)||0,r=(null==t?void 0:t.y)||0,i=(null==t?void 0:t.z)||0,o=t.duration||1e3;t.animations=[{type:"static",duration:o,x:e,y:r,z:i}]}return t},t}(),$=function(){function t(){}return t.prototype.process=function(t,n){return"object"!=typeof t?(console.warn(s.t("Unknown danmaku format")+" : "+t+" index: + "+n),p.dispatch(a.DANMAKU_FORMAT_ERROR,{content:t,index:n}),!1):t},t}(),Q=function(){function t(){this.list=[new $,new W,new q,new X]}return t.prototype.process=function(t,n){var e=t;return this.list.forEach((function(t){var r=t.process(e,n);if(!1===r)return!1;e=r})),e},t}(),tt=function(){function t(){}return t.prototype.process=function(t,n){if(null==n?void 0:n.delay){var e=parseInt(n.delay,10),r=t.startTime()+e;t.setParams({start:r})}return t},t}(),nt=function(){function t(){this.idList=[]}return t.prototype.process=function(t,n){if(!(null==n?void 0:n.start)&&(null==n?void 0:n.then)){var e=this.idList[n.then]||0;t.setParams({start:e})}return(null==n?void 0:n.id)&&(this.idList[n.id]=t.startTime()+t.getAnimation().getDuration()),this.idList._LAST_=t.startTime()+t.getAnimation().getDuration(),t},t}(),et=function(){function t(){this.list=[new nt,new tt]}return t.prototype.process=function(t,n){var e=t;return this.list.forEach((function(r){var i=r.process(t,n);if(!1===i)return!1;e=i})),e},t}(),rt=function(){function t(){this.tagList=[]}return t.prototype.parser=function(t){try{var n=JSON.parse(t);return this.getDanmaku(n)}catch(t){return p.dispatch(a.DANMAKU_JSON_INVALID,s.t("Danmaku parser fail")),console.warn(s.t("Danmaku parser fail")),console.warn(t),[]}},t.prototype.getDanmaku=function(t){var n=this,e=[],r=new Q,i=new et;return null==t||t.forEach((function(t,o){var a=r.process(t,o);if(a){var s=G.getDanmakuInstance(a.type);s.setParams({start:null==a?void 0:a.start,content:null==a?void 0:a.content,style:null==a?void 0:a.style,animation:Y.getAnimations("list",{animations:null==a?void 0:a.animations}),child:n.getDanmaku(a.childs)}),i.process(s,t)&&e.push(s)}})),e},t}(),it=function(){function t(){}return t.prototype.parser=function(t){try{var n=JSON.parse(t);return this.getDanmaku(n)}catch(t){return p.dispatch(a.DANMAKU_JSON_INVALID,s.t("Danmaku parser fail")),console.warn(s.t("Danmaku parser fail")),console.warn(t),[]}},t.prototype.getDanmaku=function(t){var n=this,e=[];return t.forEach((function(t){var r=G.getDanmakuInstance("text"),i=t.linear?[0,0,1,1]:[.42,0,1,1];r.setParams({content:t.content,start:t.start||0,style:n.getStyle(t),animation:Y.getAnimations("list",{cubic:i,animations:n.getAnimations(t.animations||[])})}),e.push(r)})),e},t.prototype.getStyle=function(t){return{fontSize:(t.size||40)+"px",color:t.color||"#000",fontWeight:t.weight||400,textShadow:t.shadow?"5px 5px 5px rgba(0,0,0,0.4)":"","-webkit-text-stroke":(t.stroke||0)+"px "+(t.strokeColor||"#000"),fontFamily:t.font||"黑体",zIndex:t.zIndex||1}},t.prototype.getAnimations=function(t){var n,e=[];if(!Array.isArray(t)||t===[])return e;t.forEach((function(t){var r=t.x,i=void 0===r?0:r,o=t.y,a=void 0===o?0:o,s=t.opacity,u=void 0===s?1:s,c=t.rx,l=void 0===c?0:c,p=t.ry,h=void 0===p?0:p,f=t.rz,d=void 0===f?0:f,y=t.scale,g=void 0===y?1:y;0===g&&(g=1);var v={type:"group",animations:[]};if(n){var m=n.x,A=void 0===m?0:m,b=n.y,w=void 0===b?0:b,x=n.duration,_=void 0===x?1e3:x,S=n.opacity,C=void 0===S?1:S,O=n.rx,k=void 0===O?0:O,E=n.ry,T=void 0===E?0:E,D=n.rz,L=void 0===D?0:D;v.animations.push({type:"translate",duration:_,path:{x2:i-A,y2:a-w}}),v.animations.push({type:"opacity",duration:_,opacity:[C,u]}),v.animations.push({type:"rotateX",duration:_,angle:{end:l-k}}),v.animations.push({type:"rotateY",duration:_,angle:{end:h-T}}),v.animations.push({type:"rotateZ",duration:_,angle:{end:d-L}}),v.animations.push({type:"scale",duration:_,scale:{x2:g,y2:g,z2:g}}),console.log(g)}else v.animations.push({type:"static",duration:0,x:i,y:a}),v.animations.push({type:"rotateX",duration:0,angle:{end:l}}),v.animations.push({type:"rotateY",duration:0,angle:{end:h}}),v.animations.push({type:"rotateZ",duration:0,angle:{end:d}}),v.animations.push({type:"scale",duration:0,scale:{x2:g,y2:g,z2:g}});e.push(v),n=t}));var r=n.duration,i=void 0===r?1e3:r;return e.push({type:"static",duration:i}),e},t}(),ot=function(){function t(){}return t.getInstance=function(t){var n=this.parser[t];return n||(n=this.parser.code),new n},t.parser={code:rt,mode7:it},t}(),at=function(){function t(t){this.stageList=[],this.pauseStatus=!0,this.timeStamp=0,this.time=0,this.skipStatus=!1,this.danmakuFunction={},this.containers=t,this.canvasStyle=window.getComputedStyle(t),this.initContainer();var n=this;!function t(){n.pauseStatus||n.refresh(),requestAnimationFrame(t)}()}return t.prototype.getContainersSize=function(){return{width:new u(parseInt(this.canvasStyle.width,10)),height:new u(parseInt(this.canvasStyle.height,10))}},t.prototype.registStage=function(t,n){return!this.stageList[n]&&(this.stageList[n]=t,!0)},t.prototype.mount=function(){var t=this;this.stageList.forEach((function(n,e){var r=t.getDIV(),i=g.getRenderInstance(n.rendererType());i.setCanvasContainer(r),n.stageRenderer(i);var o=n.timeLineType(),a=l.getTimeLine(o);n.timeLine(a);var u=n.attachedType();t.danmakuFunction[u]?t.resetDanmaku(e):console.warn(s.t("danmaku get function is null :"+u)),i.updateCanvasStyle(t.getCanvasStylByStage(n))}))},t.prototype.initContainer=function(){i.debug&&this.containers.classList.add("danmaku-containers-debug"),this.containers.classList.add("danmaku-containers")},t.prototype.resize=function(){var t=this;this.stageList.forEach((function(n){var e=n.getRenderer();e&&e.updateCanvasStyle(t.getCanvasStylByStage(n))}))},t.prototype.getDIV=function(){var t=document.createElement("div");return t.classList.add("stage"),this.containers.appendChild(t),t},t.prototype.getCanvasStylByStage=function(t){var n=t.stageSize(this.getContainersSize()),e=t.stageBackgroundColor(this.getContainersSize());return{position:t.stagePosition(this.getContainersSize(),n),color:e,size:n}},t.prototype.refresh=function(){var t=this;this.time=Date.now()-this.timeStamp,this.stageList.forEach((function(n){n.refresh(t.time)}))},t.prototype.pause=function(){this.pauseStatus||(this.pauseStatus=!0)},t.prototype.start=function(){this.pauseStatus&&(this.timeStamp=Date.now()-this.time,this.pauseStatus=!1)},t.prototype.skip=function(t){this.pauseStatus?this.time=t:this.timeStamp=Date.now()-t},t.prototype.reset=function(){this.stageList.forEach((function(t){t.reset()})),p.dispatch(a.DANMAKU_STAGE_RESET,{}),this.pauseStatus=!0,this.timeStamp=0,this.time=0,this.skipStatus=!1},t.prototype.getTime=function(){return this.time},t.prototype.addGetDanmakuFunction=function(t,n){this.danmakuFunction[t]=n},t.prototype.resetDanmaku=function(t){if(this.stageList[t]){p.dispatch(a.DANMAKU_LOAD_START,{});var n=this.stageList[t],e=n.getTimeLine();e.reset();var r=this.danmakuFunction[n.attachedType()];r&&r((function(t){t.forEach((function(t){ot.getInstance(n.attachedType()).parser(t).forEach((function(t){console.log(t.getAnimation()),e.addDanmaku(t)})),p.dispatch(a.DANMAKU_LOAD_DONE,{})}))}))}},t}(),st=function(t,n,e){if(e||2===arguments.length)for(var r,i=0,o=n.length;i