从0开始写一个播放器系列-数据格式定义以及工具类

数据格式定义

工欲善其事必先利其器, 定义一套数据格式有助于理顺思路, 所以开始编写代码第一步应该先定义一套数据格式

回顾一下设想的调用方式 new dpVideo(parentDom,urlList,options)

parentDom为外层盒子

urlList 为video的src

options 为配置项

这里的urlList格式和options需要设计数据格式

首先说一下urlList的数据格式吧

urlList数据格式

允许用户传入字符串 , 数组两种形式的数据

设想中需要拥有切换下一P, 选择清晰度, 选集的功能

所以目前暂定的数据格式如下

// 格式1 : “http://asdasd”
// 格式2 : [{lebal:“张三的幸福生活1”,defaultId:0,urlList:[{id:0,url:‘http://asdasd’,label:‘360P’}]}},“http://asdasd”]

格式1说明

只传入字符串的情况下不出现切换下一P, 选择清晰度, 选集的功能

格式2说明

当数组长度等于1或者当前选中数组的下标 等于数组长度-1时, 不出现切换下一集以及选集按钮

当前选中的元素为字符串或者当前选中元素的urlList长度==1时, 不出现选择清晰度

options数据格式就是前面文章提到的

interface optionsFormat{
    barrageShow?:boolean, // 是否开启弹幕功能, 默认为true
    barrageList?:Array<{
        msg:"",//弹幕内容
        time:Number // 弹幕发送时间,相对于视频的毫秒数
    }>// 弹幕数组默认为空,上方标识弹幕数据格式
    barrageScope?: number, // 默认为1500毫秒, 在可视区域停留时间
    title?: {
        // 标题
        show: boolean, // 标题是否显示
        label: string // 标题显示内容
    },
    // 控制条显示
    controller?: boolean, // 默认显示
    // 控制条配置项, 如果用户手动指定则按照用户手动指定的解析
    controllerOption?: {
        play?: boolean, // 播放按钮, 默认为true
        next?: boolean, // 下一个按钮, 默认为false, 如果传入的videoUrl的格式为2或者4的话则默认为true
        clarity?: boolean, //选择清晰度, 默认为false, 如果传入的videoUrl格式为3,4为true
        volume?: boolean, // 音量默认为true
        playbackRate?: boolean, // 播放速度默认为true
        playbackRateList?: Array<{ speed:number,label:string,default?:boolean}>, // 播放速度数组默认值
        togglePip?: boolean, // 画中画 默认true
        documentFull?: boolean, // 网页全屏 默认true
        windowFull?: boolean, // 全屏 默认true
        Mirror?: boolean // 镜像 默认false
    },
    _parentSelf?:any
}

文件中代码如下

src\core\baseType.d.ts

// videoList 四种数据格式
// 格式1 : "http://asdasd"
// 格式2 : [{defaultId:0,urlList:[{id:0,url:'http://asdasd',label:'360P'}]}}]


export interface urlFormat1 {
    defaultId:number|string,
    urlList: Array<urlFormat2>
}
export interface urlFormat2 {
    id:number,
    url:string,
    label:string
}
// 配置项数据类型
export interface optionsFormat {
    barrageShow?:boolean, // 是否开启弹幕功能, 默认为true
    barrageList?:Array<{
        msg:"",//弹幕内容
        time:Number // 弹幕发送时间,相对于视频的毫秒数
    }>// 弹幕数组默认为空,上方标识弹幕数据格式
    barrageScope?: number, // 默认为1500毫秒, 在可视区域停留时间
    title?: {
        // 标题
        show: boolean, // 标题是否显示
        label: string // 标题显示内容
    },
    // 控制条显示
    controller?: boolean, // 默认显示
    // 控制条配置项, 如果用户手动指定则按照用户手动指定的解析
    controllerOption?: {
        play?: boolean, // 播放按钮, 默认为true
        next?: boolean, // 下一个按钮, 默认为false, 如果传入的videoUrl的格式为2或者4的话则默认为true
        clarity?: boolean, //选择清晰度, 默认为false, 如果传入的videoUrl格式为3,4为true
        volume?: boolean, // 音量默认为true
        playbackRate?: boolean, // 播放速度默认为true
        playbackRateList?: Array<{ speed:number,label:string,default?:boolean}>, // 播放速度数组默认值
        togglePip?: boolean, // 画中画 默认true
        documentFull?: boolean, // 网页全屏 默认true
        windowFull?: boolean, // 全屏 默认true
        Mirror?: boolean // 镜像 默认false
    },
    _parentSelf?:any
}

// 导出集成的UrlListType
export type UrlListType = Array<string | urlFormat1> | string;

工具类编写

工具类是用来提炼一些常用代码和解耦的

因为不是写html,所以需要一套完整的设置HTML,创建HTML标签的方法

目前我写下来有关HTML操作的主要是

  • 元素挂载Style
  • 元素挂载class
  • 元素绑定事件
  • 创建元素

还需要判断urlList的类型

以及格式化时间的函数

元素挂载Style实现
export function styleList(dom: HTMLElement, styleList: object) {
    for (let item in styleList) {
        dom.style[item] = styleList[item];
    }
}
元素挂载class实现
export function classNameList(dom: HTMLElement, className: Array<string>) {
    dom.classList.add(...(className.filter(item=>item.length!=0)))
}
元素绑定事件实现
export function bindMethods(dom: HTMLElement, methods: object) {
    let keys = Object.keys(methods);
    for (let i = 0; i < keys.length; i++) {
        dom.addEventListener(keys[i], methods[keys[i]])
    }
}
创建元素实现

创建元素比较复杂, 使用递归实现

interface DomList {
    tag: string, // 创建的tag名
    innerText?: string, // tag中的innerText
    className?: Array<string>, // tag类名
    style?: object, // tag行内样式
    children?: Array<DomList>, // tag子元素
    methods?: object // tag绑定的事件
}

// 创建div的渲染函数
export function render( domList: DomList,parentDom: HTMLElement | null = null): HTMLElement {
    let c_dom = document.createElement(domList.tag);
    if (domList.innerText) {
        c_dom.innerText = domList.innerText;
    }
    if (domList.className) {
        classNameList(c_dom, domList.className);
    }
    if (domList.style) {
        styleList(c_dom, domList.style);
    }
    if (domList.methods) {
        bindMethods(c_dom, domList.methods);
    }
    if (domList.children && domList.children.length > 0) {
        for (let i = 0; i < domList.children.length; i++) {
            render(domList.children[i], c_dom);
        }
    }
    if (parentDom !== null) {
        parentDom.append(c_dom);
        return parentDom;

    } else {
        return c_dom;
    }
}

适用方式大概是下面这个样子的(使用方式怎么说呢, 感觉和jsx有些许相似, 也可是使用map方法,具体使用方式后面控制条实现篇说)

let volumeContrller = render({
            tag: "div",
            className: ["dp-volume-contrller"],
            children: [
                {tag: "span", className: ["dp-volume-msg"], innerText: this.video.volume * 100 + ""},
                {
                    tag: "div",
                    className: ["dp-volume-contrller-box"],
                    children: [
                        {
                            tag: "div",
                            className: ["dp-volume-contrller-point"],
                            style: {
                                bottom: 100 * this.video.volume + "px"
                            },
                            methods: {
                                mousedown: () => {
                                    let _that = this
                                    this.video.muted = false;

                                    function mouseMoveFn(e) {
                                        if (_that.video.volume + (e.movementY * -1) / 100 > 1 || _that.video.volume + (e.movementY * -1) / 100 < 0) {
                                            return;
                                        }
                                        _that.video.volume += (e.movementY * -1) / 100
                                        _that.changeVolumeDom();

                                    }

                                    function mouseUpFn() {
                                        window.removeEventListener("mousemove", mouseMoveFn)
                                        window.removeEventListener("mouseup", mouseUpFn)
                                    }

                                    window.addEventListener("mousemove", mouseMoveFn)
                                    window.addEventListener("mouseup", mouseUpFn)
                                }
                            }
                        },
                        {
                            tag: "div",
                            className: ["dp-volume-contrller-bg"],
                            style: {height: 100 * this.video.volume + "px"}
                        },
                    ]
                }
            ]

        })
判断urlList的类型实现
export function whatUrlType(obj): number {
    // 格式进行修改 :
    // 格式1: "http://asdasd"
    // 格式2:  ["http://asdasd",{defaultId:0,urlList:[{id:0,url:'http://asdasd',label:'360P'}]}]
    let flag = 0;
    if (Object.prototype.toString.call(obj) === '[object String]') {
        flag = 1;
    } else if (Object.prototype.toString.call(obj) === "[object Array]") {
        flag = 2;
    }
    return flag;
}
格式化时间实现
export function formatTime (time:number):string {
    // 此处只需要 分钟:秒钟 的格式
    let ceilTime = Math.ceil(time);
    let m = Math.floor(ceilTime/60);
    let s = ceilTime % 60;
    let r_m = m < 10 ? "0" + m : m;
    let r_s = s < 10 ? "0" + s : s;
    return r_m + ":" + r_s;
}

src\core\utils.ts

// 检查urlListType到底是哪种类型
export function whatUrlType(obj): number {
    // 格式进行修改 :
    // 格式1: "http://asdasd"
    // 格式2:  ["http://asdasd",{defaultId:0,urlList:[{id:0,url:'http://asdasd',label:'360P'}]}]
    let flag = 0;
    if (Object.prototype.toString.call(obj) === '[object String]') {
        flag = 1;
    } else if (Object.prototype.toString.call(obj) === "[object Array]") {
        flag = 2;
    }
    return flag;
}

// 获取元素真实属性
export function getStyle(element, style) {
    return element.currentStyle ? element.currentStyle[style] : getComputedStyle(element)[style];
}

// 元素挂载style
export function styleList(dom: HTMLElement, styleList: object) {
    for (let item in styleList) {
        dom.style[item] = styleList[item];
    }
}

// 元素挂载className
export function classNameList(dom: HTMLElement, className: Array<string>) {
    dom.classList.add(...(className.filter(item=>item.length!=0)))
}

// 绑定事件
export function bindMethods(dom: HTMLElement, methods: object) {
    let keys = Object.keys(methods);

    for (let i = 0; i < keys.length; i++) {
        dom.addEventListener(keys[i], methods[keys[i]])
    }
}

interface DomList {
    tag: string, // 创建的tag名
    innerText?: string, // tag中的innerText
    className?: Array<string>, // tag类名
    style?: object, // tag行内样式
    children?: Array<DomList>, // tag子元素
    methods?: object // tag绑定的事件
}

// 创建div的渲染函数
export function render( domList: DomList,parentDom: HTMLElement | null = null): HTMLElement {
    let c_dom = document.createElement(domList.tag);
    if (domList.innerText) {
        c_dom.innerText = domList.innerText;
    }
    if (domList.className) {
        classNameList(c_dom, domList.className);
    }
    if (domList.style) {
        styleList(c_dom, domList.style);
    }
    if (domList.methods) {
        bindMethods(c_dom, domList.methods);
    }
    if (domList.children && domList.children.length > 0) {
        for (let i = 0; i < domList.children.length; i++) {
            render(domList.children[i], c_dom);
        }
    }
    if (parentDom !== null) {
        parentDom.append(c_dom);
        return parentDom;

    } else {
        return c_dom;
    }
}

// 格式化时间
export function formatTime (time:number):string {
    let ceilTime = Math.ceil(time);
    let m = Math.floor(ceilTime/60);
    let s = ceilTime % 60;
    let r_m = m < 10 ? "0" + m : m;
    let r_s = s < 10 ? "0" + s : s;
    return r_m + ":" + r_s;
}

本来打算这篇写完核心类实现的…

但是篇幅不知不觉就挺长的了…估计再写下去也没人看的下去了

下篇写核心类实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值