first commit

This commit is contained in:
编码猿
2024-09-27 01:25:59 +08:00
commit 546fffb620
78 changed files with 30293 additions and 0 deletions

1
src/core/.pydio Normal file
View File

@@ -0,0 +1 @@
1df3ad8a-5c58-442f-93c0-3e72b9fd3577

View File

@@ -0,0 +1 @@
966472fa-a3be-4729-a71b-ae988035f1e7

View File

@@ -0,0 +1,18 @@
import OptionParamsModel from "../model/OptionParams.model";
import { ClassName } from "../config/ClassName.config";
export function Dom() {
return function (target: any, propertyName: string) {
setTimeout(()=> {
try {
target[propertyName] = OptionParamsModel.VideoDom.querySelector(`.${ClassName[propertyName]}`)
} catch (e) {
throw new Error(`错误信息:需要绑定事件的类名:${ClassName[propertyName]}不存在
1请检查类名是否存在
2删除 ui/*.ui.ts 代码中对应的 Dom() 装饰器 和 方法
`)
}
}
,300)
}
}

View File

@@ -0,0 +1,36 @@
import IocModel from "../model/Ioc.model";
/**
* 收集类依赖
* @constructor
*/
export function Injectable() {
return (_constructor: { new (...args: any[]): {}; }) => {
if(IocModel.classPool.indexOf(_constructor) !== -1) {
throw new Error('无需重复收集类');
} else {
//注册
IocModel.classPool = [_constructor]
}
}
}
/**
* 将类依赖实例化然后注入到被装饰的属性中
* @constructor
*/
export function Inject() {
return function (target: any, propertyName: string) {
/**
* 使用 reflect-metadata 提供的内置 类型元数据键 design:type 通过反射拿到被装饰属性的类型
* 也就是 类属性要实例化 的 service 类
*/
const propertyType: any = Reflect.getMetadata('design:type', target, propertyName);
if (IocModel.classPool.indexOf(propertyType) == -1) {
throw new Error('被装饰的属性所属的变量类型类,没有被装饰器@Injectable()注入,请检查!');
} else {
// 从存取器的数组中通过下标取出被装饰属性对应的service类然后实例化这个类在放入被装饰的属性中
target[propertyName] = new (IocModel.classPool[IocModel.classPool.indexOf(propertyType)])()
}
}
}

1
src/core/config/.pydio Normal file
View File

@@ -0,0 +1 @@
fbfd9291-1d66-4dcb-bcd8-abbe474d7c84

View File

@@ -0,0 +1,22 @@
import { Config } from "./index.config";
export enum ClassName {
// 播放按钮的div class 名称
Play = "icon-bofang",
Video = "a_video",
Pause = "icon-tingzhi1",
Loop = "icon-xunhuan",
Stop = "icon-tingzhi1",
Active = "active",
High = "icon-huaban-",
PictureInPicture = "icon-ai223",
Rotate = "icon-xuanzhuan1",
Image = "icon-jingxiang2",
ScreenShot = "icon-jietu_huaban",
VideoProgress = "a_progress",
VideoTime = "a_videotime",
Speed = "icon-suduspeed8",
FullScreen = "icon-quanping_huaban",
playList = "icon-liebiao",
SideMenu = "a_side_menu"
}

View File

@@ -0,0 +1,9 @@
export enum VideoMimeType {
HLS = "application/x-mpegurl",
TS = "video/MP2T",
MP4 = "video/mp4",
STREA = "application/octet-stream",
FLV = 'video/x-flv',
MOV = 'video/quicktime',
AVI = 'video/x-msvideo',
}

View File

@@ -0,0 +1,61 @@
import { OptionParamsTypes } from "../types/OptionParams.types";
export class Config {
public static DefalutOptionParams: OptionParamsTypes = {
el: '#app', // 父节点
url: 'http://demo-videos.qnsdk.com/movies/qiniu.mp4', // 视频地址参数2[{title:'高清', url: ''},{title: '普清', url: ''}]
title: '欢迎使用 AVideo 播放器!',
layout: 'DefaultLayout',// 选择的播放器布局主题
width: '100%', // 高度
height: '400px', // 宽度
autoplay: false, // 是否自动播放
muted: false, // 是否静音 true 静音false 不静音
volume: 1, // 初始音量 0 ~ 1 之间
poster: '',// 视频预览图地址
playList: [{title:'1',url:'222'}], // 显示视频播放列表
controls: { // 配置视频底部 播放控件
image: true, // 是否需要镜像功能
setting: true, // 是否需要设置功能
rotate: true, // 是否开启旋转开启后视频旋转默认顺时针90°度数。参数还支持传入正负的度数例如-30将会逆时针使用用户传入度数进行旋转
play: true, // 是否需要播放按钮
loop: true, // 是否需要循环播放按钮
speed: true, // 是否需要倍速,如果传入数组,类似:[0.3, 0.5, 1, 1.5, 2, 3],那么采用用户传入
FullScreen: true, // 是否需要全屏按钮
down: true, // 是否需要下载按钮
PictureInPicture: true, // 是否需要画中画按钮
memory: true, // 是否开启记忆播放 参数2{ tips: '是否继续从2:00开始播放' } tips 自定义提示文字
ScreenShot: true, // 是否需要截图,
theme: true, // 是否显示皮肤切换的按钮
progressMarkers: [],
vip: {},
},
paused: (video)=> {}, // 视频被暂停的回调
play: (video)=> {}, // 视频开始播放的回调
finish: (video)=> {}, // 视频播放完成的回调
error: (video)=> {}, // 视频播放出错的回调
timeupdate: (video)=> {}, // 视频播放时间更新的回调
};
public static IconConfig = {
topIcon: [
{ title: '下载', icon: 'icon-down1', key: 'down' },
{ title: '列表', icon: 'icon-liebiao', key: 'playList' },
{ title: '设置', icon: 'icon-setting', key: 'setting' },
],
leftIcon: [
{ title: '播放', icon: 'icon-bofang', key: 'play' },
{ title: '循环播放', icon: 'icon-xunhuan', key: 'loop' },
],
rightIcon: [
{ title: '皮肤', icon: 'icon-jingzi', key: 'theme' },
{ title: '高清', icon: 'icon-huaban-', key: 'high' },
{ title: '旋转', icon: 'icon-xuanzhuan1', key: 'rotate' },
{ title: '镜像', icon: 'icon-jingxiang2', key: 'image' },
{ title: '截图', icon: 'icon-jietu_huaban', key: 'ScreenShot' },
{ title: '倍速', icon: 'icon-suduspeed8', key: 'speed' },
{ title: '画中画', icon: 'icon-ai223', key: 'PictureInPicture' },
{ title: '全屏', icon: 'icon-quanping_huaban', key: 'FullScreen' },
]
}
}

43
src/core/init.ts Normal file
View File

@@ -0,0 +1,43 @@
import { OptionParamsTypes } from "./types/OptionParams.types";
import OptionParamsModel from "./model/OptionParams.model";
import { Config } from "./config/index.config";
import { ApplicationUI } from "./ui/index.ui";
import { Inject, Injectable } from "./annotation/Ioc.annotation";
/**
* 核心必传参数的校验
*/
export class Init {
@Inject()
private ApplicationUI!: ApplicationUI;
private OptionParams: OptionParamsTypes;
constructor(OptionParams: OptionParamsTypes) {
this.OptionParams = OptionParams;
this.checkParams()
}
public checkParams(): void {
if (this.OptionParams.hasOwnProperty("el") && this.OptionParams.hasOwnProperty("url")) {
if (this.OptionParams.controls && Object.keys(this.OptionParams.controls).length == 0) {
throw new Error("请勿传入空controls的配置")
}
this.OptionParams.playList === undefined
? delete Config.DefalutOptionParams.playList
: null
// 将用户传入的参数和默认参数进行合并,如果值冲突,优先采用用户的配置来覆盖默认值
Object.assign(Config.DefalutOptionParams, this.OptionParams)
// 保存配置到存取器,供全局使用
OptionParamsModel.OptionParams = Config.DefalutOptionParams;
this.ApplicationUI.GenerateDom();
} else {
throw new Error("请检查必传参数el 和 url是否传入")
}
}
}

1
src/core/model/.pydio Normal file
View File

@@ -0,0 +1 @@
3564163c-013d-4d8c-94bc-a306dd223191

View File

@@ -0,0 +1,13 @@
class IocModel {
private _classPool: Array<{ new (...args: any[]): {}; }> = [];
get classPool(): Array<{ new(...args: any[]): {} }> {
return this._classPool;
}
set classPool(value: Array<{ new(...args: any[]): {} }>) {
this._classPool = [...value, ...this._classPool]
}
}
export default new IocModel();

View File

@@ -0,0 +1,24 @@
import { OptionParamsTypes } from "../types/OptionParams.types";
class OptionParamsModel {
private _OptionParams: OptionParamsTypes;
private _VideoDom: HTMLElement;
get VideoDom(): HTMLElement {
return this._VideoDom;
}
set VideoDom(value: HTMLElement) {
this._VideoDom = value;
}
get OptionParams(): OptionParamsTypes {
return this._OptionParams;
}
set OptionParams(value: OptionParamsTypes) {
this._OptionParams = value;
}
}
export default new OptionParamsModel();

1
src/core/types/.pydio Normal file
View File

@@ -0,0 +1 @@
df13c4ef-91b6-4544-a480-23a706749d0a

View File

@@ -0,0 +1,62 @@
// 播放地址
interface UrlItem {
title: string,
url: string
}
// 记忆打点配置
interface MemoryItem {
tips: string
}
// 视频打点 的配置数组
interface progressMarkersItem {
title: string,
theme?: number,
offset: number,
img?: string,
describe?: string
}
// 视频底部ui控件
export interface ControlsTypes {
setting?: boolean,
rotate?: boolean | number,
image?: boolean,
play?: boolean,
loop?: boolean,
speed?: boolean | number[],
FullScreen?: boolean,
down?: boolean,
PictureInPicture?: boolean,
memory?: boolean | MemoryItem,
ScreenShot?: boolean,
theme?: boolean,
progressMarkers: Array<progressMarkersItem>,
vip: {
time?: number,
tip?: string,
popup?: string
}
}
// 主要配置
export interface OptionParamsTypes {
el: string,
url: string | Array<UrlItem>,
title: string,
layout?: string,
width?: string,
height?: string,
autoplay?: boolean,
muted?: boolean,
volume?: number,
poster?: string,
playList?: Array<UrlItem>,
controls?: ControlsTypes,
paused?: (video: HTMLMediaElement) => any;
play?: (video: HTMLMediaElement) => any;
finish?: (video: HTMLMediaElement) => any;
error?: (video: HTMLMediaElement) => any;
timeupdate?: (video: HTMLMediaElement) => any;
}

1
src/core/ui/.pydio Normal file
View File

@@ -0,0 +1 @@
c86ae987-557c-4c45-ae05-8678ae14b9a5

239
src/core/ui/controls.ui.ts Normal file
View File

@@ -0,0 +1,239 @@
import OptionParamsModel from "../model/OptionParams.model";
import { Utils } from "../utils/index.utils";
import { Inject, Injectable } from "../annotation/Ioc.annotation";
import { Dom } from "../annotation/Dom.annotation";
import {ClassName} from "../config/ClassName.config";
/**
* 底部控件条的基础功能设置
*/
export class ControlsUi {
@Dom()
private readonly Play!: HTMLElement;
@Dom()
private readonly Video!: HTMLMediaElement & HTMLVideoElement;
@Dom()
private readonly Loop!: HTMLElement;
@Dom()
private readonly PictureInPicture!: HTMLElement;
@Dom()
private readonly Rotate!: HTMLElement;
@Dom()
private readonly Image!: HTMLLIElement;
@Dom()
private readonly ScreenShot!: HTMLLIElement;
@Dom()
private readonly Speed!: HTMLLIElement;
@Dom()
private readonly FullScreen!: HTMLLIElement;
@Dom()
private readonly playList!: HTMLLIElement;
@Inject()
private readonly Utils!: Utils;
/**
* 播放按钮被点击,播放视频
* @constructor
*/
public BindEventPlay(): void {
this.Play.onclick = async () => {
this.Video.paused ? await this.Video.play() : await this.Video.pause()
this.Play.classList.toggle(ClassName.Stop)
}
}
public BindEventPlayList(): void {
let SideMenu = this.Utils.$(`.${ClassName.SideMenu}`);
this.playList.onclick = async () => {
if (SideMenu.style.right == "0px") {
this.Utils.$(`.${ClassName.SideMenu}`).style.right = '-250px'
} else {
this.Utils.$(`.${ClassName.SideMenu}`).style.right = '0'
}
}
}
/**
* 视频全屏
* @constructor
*/
public BindEventFullScreen(): void {
this.FullScreen.onclick = async () => {
if (!document.fullscreenElement) {
this.Video.parentElement.style.width = '100%'
this.Video.parentElement.style.height = '100%'
await document.documentElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
this.Video.parentElement.style.width = OptionParamsModel.OptionParams.width
this.Video.parentElement.style.height = OptionParamsModel.OptionParams.height
await document.exitFullscreen();
}
}
this.FullScreen.classList.toggle(ClassName.Active)
}
}
/**
* 截图按钮
* @constructor
*/
public BindEventScreenShot(): void {
let scale = 0.65;
this.ScreenShot.onclick = () => {
var canvas = document.createElement("canvas");
canvas.width = this.Video.videoWidth * scale;
canvas.height = this.Video.videoHeight * scale;
canvas.getContext('2d')
.drawImage(this.Video, 0, 0, canvas.width, canvas.height);
this.Utils.downLoadFile(canvas.toDataURL('image/jpeg',1.0), 'image/jpeg')
}
}
/**
* 循环播放的开关
* @constructor
*/
public BindEventLoop(): void {
this.Loop.onclick = () => {
this.Video.loop ? this.Video.loop = false : this.Video.loop = true
this.Loop.classList.toggle(ClassName.Active)
}
}
/**
* 视频镜像功能
* @constructor
*/
public BindEventImage(): void {
// 延迟解决 ondblclick 时候会触发 onclick 的问题
let ImageStatus: boolean = true, timeId = null; // true rotateY 镜像false rotateX 镜像
// 双击视频恢复
this.Image.ondblclick = () => {
clearTimeout(timeId);
ImageStatus = true
this.Video.style.cssText = `
transform: rotateY(0deg);
`
}
// 单击镜像
this.Image.onclick = () => {
clearTimeout(timeId);
timeId = setTimeout(() => {
if (ImageStatus) {
ImageStatus = false
this.Video.style.cssText = `
transform: rotateY(180deg);
`
} else {
ImageStatus = true
this.Video.style.cssText = `
transform: rotateX(180deg);
`
}
}, 250);
}
}
/**
* TODO 视频倍速的功能 实现中...
* @constructor
*/
public BindEventSpeed(): void {
// 默认视频倍速
let defaultSpeed: number[] = [0.5, 1.0, 1.25, 1.5, 2.0];
this.Speed.onclick = () => {
// 如果传入的是数组
if (Array.isArray(OptionParamsModel.OptionParams.controls.speed)) {
defaultSpeed = OptionParamsModel.OptionParams.controls.speed;
console.log("如果传入的是数组")
// 如果传入的是true表示开启默认的倍速播放功能
} else if (OptionParamsModel.OptionParams.controls.speed == true) {
console.log("表示开启默认的倍速播放功能")
}
}
}
/**
* 画中画
* @constructor
*/
public BindEventPictureInPicture(): void {
this.PictureInPicture.onclick = () => {
if (document.pictureInPictureElement == null) {
this.Video.requestPictureInPicture();
this.PictureInPicture.classList.add(ClassName.Active)
} else {
document.exitPictureInPicture();
this.PictureInPicture.classList.remove(ClassName.Active)
}
}
/**
* 监听退出画中画功能
*/
this.Video.addEventListener('leavepictureinpicture', () => {
this.PictureInPicture.classList.remove(ClassName.Active)
this.Play.classList.toggle(ClassName.Stop)
});
}
/**
* 视频旋转
* @constructor
*/
public BindEventRotate(): void {
let currentRotate: number = 0, RotateNumber: number = 0;
this.Rotate.onclick = () => {
if (RotateNumber >= 4) {
currentRotate = 0;
RotateNumber = 0;
}
currentRotate += <boolean>OptionParamsModel.OptionParams.controls.rotate == true
? 90
: <number>OptionParamsModel.OptionParams.controls.rotate;
RotateNumber++
if (currentRotate == 180 || currentRotate == 360 ||
currentRotate == -180 || currentRotate == -360) {
this.Video.style.cssText = `
width: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(${currentRotate}deg);
`;
return false
} else {
// 视频旋转 90°270°后视频的宽度 等于 父元素的高度
this.Video.style.cssText = `
width: ${OptionParamsModel.OptionParams.height};
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(${currentRotate}deg);
`;
}
}
}
}

View File

@@ -0,0 +1,120 @@
import {Dom} from "../annotation/Dom.annotation";
import OptionParamsModel from "../model/OptionParams.model";
import { ClassName } from "../config/ClassName.config";
import { VideoMimeType } from "../config/VideoMimeType.config";
import {Utils} from "../utils/index.utils";
import {Inject} from "../annotation/Ioc.annotation";
import * as Hls from "hls.js";
/**
* 视频核心功能设置
*/
export class CoreSettingsUi {
@Dom()
private readonly Play!: HTMLElement;
@Dom()
private readonly Video!: HTMLVideoElement;
@Dom()
private readonly High!: HTMLElement;
@Inject()
private readonly Utils: Utils;
@Dom()
private readonly VideoProgress!: HTMLInputElement;
/**
* 设置视频预览图地址
* @constructor
*/
public BindEventVideoPoster(): void {
this.Video.poster = OptionParamsModel.OptionParams.poster
}
/**
* 渲染视频播放列表
* @constructor
*/
public BindEventPlayVideoList(): void {
let SideMenu: NodeListOf<HTMLDivElement> = document.querySelectorAll(`.${ClassName.SideMenu} .list_item`)
SideMenu.forEach((v,index) => {
v.onclick = () => {
this.VideoProgress.style.backgroundSize = `0% 100%`;
setTimeout(() => this.VideoProgress.value = String(0),1)
this.VideoProgress.step = '0.000000000000000001'
this.Video.src = OptionParamsModel.OptionParams.playList[index].url;
this.Play.classList.remove(ClassName.Stop)
this.Play.classList.add(ClassName.Play)
}
})
}
/**
* 设置视频宽高
* @constructor
*/
public BindEventVideoSize(): void {
this.Video.parentElement.style.width = OptionParamsModel.OptionParams.width
this.Video.parentElement.style.height = OptionParamsModel.OptionParams.height
}
/**
* 设置视频的自动播放 和 是否静音
* @constructor
*/
public BindEventVideoAutoPlayAndMuted(): void {
this.Video.autoplay = OptionParamsModel.OptionParams.autoplay;
this.Video.muted = OptionParamsModel.OptionParams.muted;
// 谷歌等众多浏览器禁止video标签有声的自动播放所以这里如果想实现有声而且自动播放视频那么需要特殊处理
// 前端用户需要开启 autoplay true 自动播放muted true 静音
if (OptionParamsModel.OptionParams.autoplay && OptionParamsModel.OptionParams.muted) {
console.log("无声,自动播放")
this.Play.classList.toggle(ClassName.Stop)
}
}
/**
* 配置单个视频地址 & 多个清晰度视频地址
* @constructor
*/
public async BindEventEndedVideoSetUrl() {
// 单个视频地址
if (typeof OptionParamsModel.OptionParams.url === "string") {
await this.Utils.CheckVideoType(<string>OptionParamsModel.OptionParams.url)
// 多清晰度视频的数组
} else {
// 数组默认第一条视频作为播放视频
await this.Utils.CheckVideoType(OptionParamsModel.OptionParams.url[0].url)
let UL: HTMLUListElement = document.createElement("ul");
UL.className = "high";
OptionParamsModel.OptionParams.url.map((val,index) => {
let li: HTMLLIElement = document.createElement("li");
li.textContent = val.title;
// 默认第一个显示激活样式
index == 0 ? li.classList.toggle(ClassName.Active) : null
li.onclick = async () => {
// 清除所有li激活类名
li.parentElement.childNodes.forEach((s: HTMLLIElement)=> s.className = "")
// 为点击的里高亮激活
li.className = ClassName.Active
// 切换视频地址
await this.Utils.CheckVideoType(val.url)
};
// 填充li到ul中
UL.appendChild(li);
});
// 鼠标悬浮 显示清晰度选择菜单
this.High.onmouseover = () => UL.style.display = "block";
this.High.onmouseout = () => UL.style.display = "none";
// 填充ul到清晰度选择的图标中
this.High.appendChild(UL);
}
}
}

View File

@@ -0,0 +1,97 @@
import {Dom} from "../annotation/Dom.annotation";
import OptionParamsModel from "../model/OptionParams.model";
import { ClassName } from "../config/ClassName.config";
import {Inject} from "../annotation/Ioc.annotation";
import { Utils } from "../utils/index.utils";
/**
* 视频播放的各种事件监听和处理
*/
export class EventListenersUi {
@Dom()
private readonly Play!: HTMLElement;
@Dom()
private readonly Video!: HTMLMediaElement;
@Dom()
private readonly VideoProgress!: HTMLInputElement;
@Dom()
private readonly VideoTime!: HTMLElement;
@Inject()
private readonly Utils: Utils;
/**
* 监听 视频数据长度发生改变的时候去获取视频的总时间,
* 并设置到页面上显示出来
* @constructor
*/
public BindEventDurationchange(): void {
this.Video.addEventListener('durationchange', () => {
this.setVideoTime(this.Video.duration)
})
}
// 播放按钮被点击,播放视频
public BindEventEnded(): void {
this.Video.addEventListener('ended', () => {
// 视频播放完成后,将暂停按钮样式改为播放状态
this.Play.classList.toggle(ClassName.Stop)
// 回调用户的事件
OptionParamsModel.OptionParams.finish(this.Video)
})
}
/**
* 视频播放过程中时间发生改变设置进度条进度
* @constructor
*/
public BindEventVideoTimeUpdate() {
this.Video.ontimeupdate = () => {
// 进度条前进值 = 当前时间 / 总视频时间 * 100
let w = this.Video.currentTime / this.Video.duration * 100;
// 设置进度条已播放过的颜色
this.VideoProgress.style.backgroundSize = `${w}% 100%`;
// 设置进度条方块前进值
this.VideoProgress.value = String(w)
// 进度条步进值
this.VideoProgress.step = '0.000000000000000001'
this.setVideoTime()
}
}
/**
* 进度条被拖动的时候
* @constructor
*/
public BindEventVideoOnProgress(): void {
this.VideoProgress.oninput = () => {
// 拿到被拖动滑块的滑动距离
let offsetX = <any>this.VideoProgress.value;
// 滑动后对应的视频播放时间 = 滑动距离 / 总距离 * 时间总时间
this.Video.currentTime = offsetX / 100 * this.Video.duration;
// 设置进度条已播放过的颜色
this.VideoProgress.style.backgroundSize = `${offsetX}% 100%`;
// 拖动的时候自动播放了,所以把播放按钮改成暂停按钮
this.Play.classList.add(ClassName.Stop)
this.setVideoTime()
}
}
/**
* 设置视频的 当前播放时间 / 视频总时间
*/
public setVideoTime(time?: number): void {
if (time === undefined) {
this.VideoTime.innerText = `${this.Utils.formatTime(this.Video.currentTime)}/${this.Utils.formatTime(this.Video.duration)}`;
} else {
this.VideoTime.innerText = `${this.Utils.formatTime(this.Video.currentTime)}/${this.Utils.formatTime(time)}`;
}
}
}

38
src/core/ui/index.ui.ts Normal file
View File

@@ -0,0 +1,38 @@
/// <reference path = "../../types/index.d.ts" />
import OptionParamsModel from "../model/OptionParams.model";
import { Utils } from "../utils/index.utils";
import { Inject, Injectable } from "../annotation/Ioc.annotation";
import { ControlsUi } from "./controls.ui";
import { EventListenersUi } from "./eventListeners.ui";
import { CoreSettingsUi } from "./coreSettings.ui";
/**
* 生成播放器的Dom树绑定各种事件处理
*/
@Injectable()
export class ApplicationUI {
@Inject()
private Utils: Utils;
private el: HTMLElement
GenerateDom() {
// 获取用户传入的最外层父节点
this.el = this.Utils.$(OptionParamsModel.OptionParams.el);
// 保存视频播放器的DOM树后面需要操作DOM树为各个按钮绑定事件处理
OptionParamsModel.VideoDom = this.Utils.ParseDom(this.Utils.getLayout());
// 自动调用传入类中方法名包含 'BindEventxxx' 的方法
// 实现各种点击事件的自动绑定
this.Utils.callMethods([
CoreSettingsUi, ControlsUi, EventListenersUi
])
// 将模板pug和参数配置渲染后得到html string转换为dom树
// 然后将已经绑定好各种事件的dom注入到用户传入的 div 中给用户使用!!
this.el.appendChild(OptionParamsModel.VideoDom);
}
}

1
src/core/utils/.pydio Normal file
View File

@@ -0,0 +1 @@
b8fc9423-3e19-4e61-af70-8d7485eac391

View File

@@ -0,0 +1,152 @@
import OptionParamsModel from "../model/OptionParams.model";
import { Config } from "../config/index.config";
import { Injectable } from "../annotation/Ioc.annotation";
import * as Hls from "hls.js";
import {VideoMimeType} from "../config/VideoMimeType.config";
import {Dom} from "../annotation/Dom.annotation";
@Injectable()
export class Utils {
@Dom()
private readonly Video!: HTMLVideoElement;
public $(el: string): any {
return document.querySelector(el)
}
/**
* 自动调用类中的事件绑定方法
* @param params 类数组
*/
public callMethods(params: {new (...args: any[]): {}; }[] ): void {
setTimeout(() =>
params.forEach((val: { new (): any; } ) =>
Object.getOwnPropertyNames(val.prototype).splice(1).filter(f =>
f.includes("BindEvent")).forEach(v =>
(new val())[v]())),400)
}
/**
* 修改元素类名
* @param dom
* @param className
*/
addClassName<E extends HTMLElement>(dom: E, className: string): void {
dom.classList.toggle(className)
}
/**
* 视同用户填入的模板传入配置渲染输出HTML
*/
public getLayout(): string {
let render = require(`../../assets/tpl/${OptionParamsModel.OptionParams.layout}.pug`)
return render({ params: Object.assign(OptionParamsModel.OptionParams, Config.IconConfig) })
}
/**
* 将 string 转换为 dom 元素
* @param arg
* @constructor
*/
public ParseDom (arg: string): HTMLElement {
var objE = document.createElement("div");
objE.innerHTML = arg;
return objE.childNodes[0] as HTMLElement;
}
public Render() {
// // 获取dom树中最后一个child也就是 script
// let oldScript = father.lastChild;
// // 从dom树中删除 script 代码因为不管是innerHTML还是appendChild dom中的script都不执行
// // 所以需要特殊处理
// father.removeChild(father.childNodes[father.childNodes.length-1])
// // 将去除 script 后的dom树插入到网页中
// el.appendChild(father);
//
// // 生成 script写入 js插入 到网页中这时候js可以执行
// let newScript = document.createElement('script');
// newScript.type = 'text/javascript';
// newScript.innerHTML = (oldScript as HTMLElement).innerHTML;
// el.appendChild(newScript);
}
/**
* 使用fetch 获取视频地址的编码类型,
* 然后采用不同的播放方式
* @constructor
*/
public async CheckVideoType(URL: string): Promise<void> {
await fetch(`http://localhost:9000/check/url?url=${URL}`)
.then((res)=>{
return res.json()
})
.then((res)=>{
switch (res.result) {
// HLS 播放支持
case VideoMimeType.HLS:
this.PlayHls(this.Video, URL)
break;
// MP4播放支持
case VideoMimeType.MP4:
this.Video.src = URL;
break;
// MP4不同编码的播放支持
case VideoMimeType.STREA:
this.Video.src = URL;
break;
default:
throw new Error("不支持播放的视频地址")
}
});
}
/**
* 下载文件
* @param href 文件地址
* @param fileType 文件类型
*/
downLoadFile(href: string, fileType = 'image/png') {
var dlLink = document.createElement('a');
dlLink.download = `AVideo_${(new Date()).valueOf().toString()}`;
dlLink.href = href;
dlLink.dataset.downloadurl = [fileType, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
}
/**
* 格式化时间
* @param t
*/
formatTime(t: number){
if (isNaN(t)) {
return "00:00:00"
}
var h: string | number = parseInt(String(t / 3600))
h = h<10?'0'+h:h
var m: string | number = parseInt(String(t % 3600 / 60))
m = m<10?'0'+m:m
var s: string | number = parseInt(String(t % 60))
s = s<10?'0'+s:s
return h+':'+m+':'+s
}
/**
* 使用插件 hls.js 播放视频
* @param Video
* @param url
* @constructor
*/
public PlayHls(Video: HTMLVideoElement, url: string) {
let hls = new Hls();
hls.loadSource(url);
hls.attachMedia(Video);
}
}