hjplayer
Version:
hjplayer, a HTML5 Player, can play flv and hls by Media Source Extension, based on typescript;
382 lines (345 loc) • 11.7 kB
text/typescript
import EventEmitter from 'eventemitter3';
import Logger from '../Utils/Logger';
import LoggingControl from '../Utils/logging-control';
import TransmuxingController from './TransmuxingController';
import Events from '../Events/index';
import TransmuxingWorker from './TransmuxingWorker';
import MediaInfo from './MediaInfo';
import getMediaTypeSupport from '../Utils/getMediaTypeSupport';
import { InitSegment, MediaSegment } from '../Interfaces/Segment';
import TempNavigatorType from '../Interfaces/TempNavigator';
import MediaConfig from '../Interfaces/MediaConfig';
import StatisticsInfoObject from '../Interfaces/StatisticsInfo';
import Metadata from '../Interfaces/Metadata';
import WEBWORKER_CMD from '../Utils/workerCmd';
import HJPlayerConfig from '../Interfaces/HJPlayerConfig';
import TSManifest from '../Interfaces/TSManifest';
const webworkify = require('webworkify-webpack');
const typeSupported = getMediaTypeSupport();
const tempNavigator: TempNavigatorType = {
vendor: window.navigator.vendor || '',
userAgent: window.navigator.userAgent
};
class Transmuxer {
/**
* 文件标签
*/
Tag: string
/**
* 事件中心
*/
private _emitter: EventEmitter
/**
* webworker
*/
private _worker?: any
/**
* web-worker是否在摧毁中
*/
private _workerDestroying?: boolean
/**
* 转码控制器
*/
private _controller?: TransmuxingController
/**
* 回调函数的包裹体
*/
e: any
constructor(mediaDataSource: MediaConfig, config: HJPlayerConfig) {
this.Tag = 'Transmuxer';
this._emitter = new EventEmitter();
const WORK: any = webworkify;
if(config.enableWorker && typeof Worker !== 'undefined') {
try {
this._worker = WORK(require.resolve('./TransmuxingWorker'));
this._workerDestroying = false;
this._worker
&& this._worker.addEventListener('message', this._onWorkerMessage.bind(this));
this._worker
&& this._worker.postMessage({
cmd: WEBWORKER_CMD.INIT,
param: [mediaDataSource, config, typeSupported, tempNavigator]
});
this.e = {
onLoggingConfigChanged: this._onLoggingConfigChanged.bind(this)
};
LoggingControl.registerListener(this.e.onLoggingConfigChanged);
this._worker
&& this._worker.postMessage({
cmd: WEBWORKER_CMD.LOG_CONFIG_CHANGE,
param: LoggingControl.getConfig()
});
} catch (error) {
Logger.error(
this.Tag,
'Error while initialize transmuxing worker, fallback to inline transmuxing'
);
delete this._worker;
this._controller = new TransmuxingController(
mediaDataSource,
config,
typeSupported,
tempNavigator
);
}
} else {
this._controller = new TransmuxingController(
mediaDataSource,
config,
typeSupported,
tempNavigator
);
}
if(this._controller) {
const ctl = this._controller;
ctl.on(Events.IO_ERROR, this._onIOError.bind(this));
ctl.on(Events.DEMUX_ERROR, this._onDemuxError.bind(this));
ctl.on(Events.INIT_SEGMENT, this._onInitSegment.bind(this));
ctl.on(Events.MEDIA_SEGMENT, this._onMediaSegment.bind(this));
ctl.on(Events.LOAD_COMPLETE, this._onLoadingComplete.bind(this));
ctl.on(Events.RECOVERED_EARLY_EOF, this._onRecoveredEarlyEof.bind(this));
ctl.on(Events.MEDIA_INFO, this._onMediaInfo.bind(this));
ctl.on(Events.METADATA_ARRIVED, this._onMetaDataArrived.bind(this));
ctl.on(Events.SCRIPTDATA_ARRIVED, this._onScriptDataArrived.bind(this));
ctl.on(Events.STATISTICS_INFO, this._onStatisticsInfo.bind(this));
ctl.on(Events.RECOMMEND_SEEKPOINT, this._onRecommendSeekpoint.bind(this));
ctl.on(Events.GET_SEI_INFO, this._onGetSeiInfo.bind(this));
ctl.on(Events.MANIFEST_PARSED, this._onMainfestParsed.bind(this));
}
}
/**
* 自我销毁
*/
destroy() {
if(this._worker) {
if(!this._workerDestroying) {
this._workerDestroying = true;
this._worker.postMessage({ cmd: WEBWORKER_CMD.DESTROY });
LoggingControl.removeListener(this.e.onLoggingConfigChanged);
this.e = null;
}
} else {
this._controller && this._controller.destroy();
delete this._controller;
}
this._emitter.removeAllListeners();
delete this._emitter;
}
on(event: string, listener: EventEmitter.ListenerFn) {
this._emitter.addListener(event, listener);
}
off(event: string, listener: EventEmitter.ListenerFn) {
this._emitter.removeListener(event, listener);
}
/**
* 是否使用了多线程
*/
hasWorker(): boolean {
return this._worker != null;
}
/**
* 开始
*/
open() {
if(this._worker) {
this._worker.postMessage({ cmd: WEBWORKER_CMD.START });
} else {
this._controller && this._controller.start();
}
}
/**
* 停止
*/
close() {
if(this._worker) {
this._worker.postMessage({ cmd: WEBWORKER_CMD.STOP });
} else {
this._controller && this._controller.stop();
}
}
/**
* 跳转的时间点
* @param milliseconds 跳转的时间点
*/
seek(milliseconds: number) {
if(this._worker) {
this._worker.postMessage({ cmd: WEBWORKER_CMD.SEEK, param: milliseconds });
} else {
this._controller && this._controller.seek(milliseconds);
}
}
/**
* 暂停转码
*/
pause() {
if(this._worker) {
this._worker.postMessage({ cmd: WEBWORKER_CMD.PAUSE });
} else {
this._controller && this._controller.pause();
}
}
/**
* 恢复转码
*/
resume() {
if(this._worker) {
this._worker.postMessage({ cmd: WEBWORKER_CMD.RESUME });
} else {
this._controller && this._controller.resume();
}
}
/**
* 收到初始化片段向上报告
* @param type 片段类型
* @param initSegment 片段数据
*/
_onInitSegment(type: string, initSegment: InitSegment) {
Promise.resolve().then(() => {
this._emitter.emit(Events.INIT_SEGMENT, type, initSegment);
});
}
/**
* 收到媒体片段向上报告
* @param type 片段类型
* @param initSegment 片段数据
*/
_onMediaSegment(type: string, mediaSegment: MediaSegment) {
Promise.resolve().then(() => {
this._emitter.emit(Events.MEDIA_SEGMENT, type, mediaSegment);
});
}
/**
* 当加载完成时向上报告
*/
_onLoadingComplete() {
Promise.resolve().then(() => {
this._emitter.emit(Events.LOAD_COMPLETE);
});
}
/**
* 从过早遇到 EOF 事件恢复后向上报告
*/
_onRecoveredEarlyEof() {
Promise.resolve().then(() => {
this._emitter.emit(Events.RECOVERED_EARLY_EOF);
});
}
/**
* 当收到解析后的媒体信息后向上提交
* @param mediaInfo 媒体信息
*/
_onMediaInfo(mediaInfo: MediaInfo) {
Promise.resolve().then(() => {
this._emitter.emit(Events.MEDIA_INFO, mediaInfo);
});
}
_onMetaDataArrived(metadata: Metadata) {
Promise.resolve().then(() => {
this._emitter.emit(Events.METADATA_ARRIVED, metadata);
});
}
_onScriptDataArrived(data: any) {
Promise.resolve().then(() => {
this._emitter.emit(Events.SCRIPTDATA_ARRIVED, data);
});
}
_onStatisticsInfo(statisticsInfo: StatisticsInfoObject) {
Promise.resolve().then(() => {
this._emitter.emit(Events.STATISTICS_INFO, statisticsInfo);
});
}
_onIOError(type: string, info: string) {
Promise.resolve().then(() => {
this._emitter.emit(Events.IO_ERROR, type, info);
});
}
_onDemuxError(type: string, info: string) {
Promise.resolve().then(() => {
this._emitter.emit(Events.DEMUX_ERROR, type, info);
});
}
/**
* 当收到 推荐的seek时间点后向上提交
* @param milliseconds 推荐的seek时间点
*/
_onRecommendSeekpoint(milliseconds: number) {
Promise.resolve().then(() => {
this._emitter.emit(Events.RECOMMEND_SEEKPOINT, milliseconds);
});
}
/**
* 收到解析到媒体增强信息后向上提交
* @param data 媒体增强信息的Uin8Array
*/
_onGetSeiInfo(data: Uint8Array) {
Promise.resolve().then(() => {
this._emitter.emit(Events.GET_SEI_INFO, data);
});
}
_onLoggingConfigChanged(config: HJPlayerConfig) {
if(this._worker) {
this._worker.postMessage({ cmd: WEBWORKER_CMD.LOG_CONFIG_CHANGE, param: config });
}
}
/**
* 收到M3U8文档解析数据后向上提交
* @param data M3U8文档解析数据, 暂时标为any
*/
_onMainfestParsed(data: TSManifest) {
Promise.resolve().then(() => {
this._emitter.emit(Events.MANIFEST_PARSED, data);
});
}
/**
* 当收到web-worker消息时的处理操作
* @param event 收到的信息体
*/
_onWorkerMessage(event: MessageEvent) {
const message = event.data;
const { data } = message;
if(message.msg === 'destroyed' || this._workerDestroying) {
this._workerDestroying = false;
this._worker && this._worker.terminate();
delete this._worker;
return;
}
switch(message.msg) {
case Events.INIT_SEGMENT:
case Events.MEDIA_SEGMENT:
this._emitter.emit(message.msg, data.type, data.data);
break;
case Events.LOAD_COMPLETE:
case Events.RECOVERED_EARLY_EOF:
this._emitter.emit(message.msg);
break;
case Events.MEDIA_INFO:
Object.setPrototypeOf(data, MediaInfo.prototype);
this._emitter.emit(message.msg, data);
break;
case Events.METADATA_ARRIVED:
case Events.SCRIPTDATA_ARRIVED:
case Events.STATISTICS_INFO:
this._emitter.emit(message.msg, data);
break;
case Events.IO_ERROR:
case Events.DEMUX_ERROR:
this._emitter.emit(message.msg, data.type, data.info);
break;
case Events.RECOMMEND_SEEKPOINT:
this._emitter.emit(message.msg, data);
break;
case Events.GET_SEI_INFO:
this._emitter.emit(message.msg, data);
break;
case Events.MANIFEST_PARSED:
this._emitter.emit(message.msg, data);
break;
case Events.WORKER_LOG:
Logger.emitter.emit(Events.HJ_PLAYER_LOG, data.type, data.msg);
break;
default:
break;
}
}
}
export default Transmuxer;