UNPKG

hjplayer

Version:

hjplayer, a HTML5 Player, can play flv and hls by Media Source Extension, based on typescript;

655 lines (551 loc) 22.8 kB
import EventEmitter from 'eventemitter3'; import Browser from '../Utils/Browser'; import MediaInfo from './MediaInfo'; import Errors from '../Errors/index'; import IOController from './IOController'; import Events from '../Events/index'; import createHLSDefaultConfig from '../Utils/createHLSDefaultConfig'; import typeSupportData from '../Interfaces/typeSupportData'; import TempNavigatorType from '../Interfaces/TempNavigator'; import Logger from '../Utils/Logger'; import Metadata from '../Interfaces/Metadata'; import ErrorData from '../Interfaces/ErrorData'; import { MediaSegment, InitSegment } from '../Interfaces/Segment'; import MediaConfig from '../Interfaces/MediaConfig'; import FileSegment from '../Interfaces/FileSegment'; import TSCodec from '../Codecs/TSCodec'; // TS解码器和转码器 import FLVCodec from '../Codecs/FLVCodec'; // FLV的解码器和转码器 import HJPlayerConfig from '../Interfaces/HJPlayerConfig'; import { TSExtraData } from '../Interfaces/TSExtraData'; import getGlobal from '../Utils/getGlobalObject'; import TSManifest from '../Interfaces/TSManifest'; const GG = getGlobal(); // Transmuxing (IO, Demuxing, Remuxing) controller, with multipart support class TransmuxingController { /** * 文件标签 */ Tag: string /** * 事件中心 */ private _emitter: EventEmitter /** * 媒体设置 */ private _mediaDataSource: MediaConfig /** * 用户设置 */ private _config: HJPlayerConfig /** * MediaSource对MP4, MEPG, MP3格式是否支持 */ private typeSupported: typeSupportData /** * 上一级缓存的关于navigator的数据, 用于在worker中解码TS音视频的必要参数 */ private navigator: TempNavigatorType /** * 当前播放的媒体片段的索引值, 在MediaConfig中无segment选项是 0 */ private _currentSegmentIndex: number /** * 媒体信息 */ private _mediaInfo: MediaInfo | null | any /** * IO 控制器, 主要负责缓存loader发送过来的数据 */ private _ioctl: IOController | null /** * 未加载时 挂起的 seek 时间点 */ private _pendingSeekTime: number | null /** * 等待解决的seek时间点 (单位: 毫秒) */ private _pendingResolveSeekPoint: number | null /** * 统计信息报告定时器 */ private _statisticsReportTimer: number | undefined | null /** * 新版解码器 待实现 */ private _mediaCodec: TSCodec | FLVCodec | null /** * 旧版解码器 */ private _demuxer: null /** * 旧版转码器 */ private _remuxer: null constructor( mediaDataSource: MediaConfig, config: HJPlayerConfig, typeSupported: typeSupportData, navigator: TempNavigatorType ) { this.Tag = 'TransmuxingController'; this._emitter = new EventEmitter(); this._config = config; this.typeSupported = typeSupported; this.navigator = navigator; this._mediaCodec = null; // fill in default IO params if not exists if(typeof mediaDataSource.cors !== 'boolean') { mediaDataSource.cors = true; } if(typeof mediaDataSource.withCredentials !== 'boolean') { mediaDataSource.withCredentials = false; } this._mediaDataSource = mediaDataSource; let totalDuration = 0; this._currentSegmentIndex = 0; // treat single part media as multipart media, which has only one segment if(!mediaDataSource.segments) { this._mediaDataSource.segments = [ { duration: mediaDataSource.duration as any, // 可能是 undefined filesize: mediaDataSource.fileSize, url: mediaDataSource.url, type: mediaDataSource.type, live: config.isLive } ]; } this._mediaDataSource.segments!.forEach((segment) => { // timestampBase for each segment, and calculate total duration segment.timestampBase = totalDuration; totalDuration += <number>segment.duration; // params needed by IOController segment.cors = mediaDataSource.cors; segment.withCredentials = mediaDataSource.withCredentials; // referrer policy control, if exist if(config.referrerPolicy) { segment.referrerPolicy = config.referrerPolicy; } }); if(!Number.isNaN(totalDuration) && this._mediaDataSource.duration !== totalDuration) { this._mediaDataSource.duration = totalDuration; } this._mediaInfo = null; this._mediaCodec = null; this._ioctl = null; this._pendingSeekTime = null; this._pendingResolveSeekPoint = null; this._statisticsReportTimer = null; } destroy() { this._mediaInfo = null; delete this._mediaDataSource; if(this._statisticsReportTimer) { this._disableStatisticsReporter(); } if(this._ioctl) { this._ioctl.destroy(); this._ioctl = null; } // 摧毁解码器 if(this._mediaCodec) { this._mediaCodec.destroy(); this._mediaCodec = null; } this._emitter.removeAllListeners(); delete this._emitter; } on(event: string, listener: EventEmitter.ListenerFn) { this._emitter.on(event, listener); } off(event: string, listener: EventEmitter.ListenerFn) { this._emitter.off(event, listener); } start() { this._loadSegment(0); this._enableStatisticsReporter(); } /** * 加载segment * @param { Number } segmentIndex - segment 索引值 * @param { Number } optionalFrom - 选择的开始地点 */ _loadSegment(segmentIndex: number, optionalFrom?: number) { this._currentSegmentIndex = segmentIndex; const dataSource = (this._mediaDataSource.segments as Array<FileSegment>)[segmentIndex]; this._ioctl = new IOController(dataSource, this._config, segmentIndex); const ioctl = this._ioctl; ioctl.onError = this._onIOException.bind(this); ioctl.onSeeked = this._onIOSeeked.bind(this); ioctl.onComplete = this._onIOComplete.bind(this); ioctl.onRedirect = this._onIORedirect.bind(this); ioctl.onRecoveredEarlyEof = this._onIORecoveredEarlyEof.bind(this); ioctl.onManifestParsed = this._onManifestParsed.bind(this); if(optionalFrom && this._mediaCodec) { this._mediaCodec.bindDataSource(this._ioctl); } else { ioctl.onDataArrival = this._onInitChunkArrival.bind(this); } ioctl.open(optionalFrom); } stop() { this._internalAbort(); this._disableStatisticsReporter(); } _internalAbort() { if(this._ioctl) { this._ioctl.destroy(); this._ioctl = null; } } pause() { // take a rest if(this._ioctl && this._ioctl.isWorking()) { this._ioctl.pause(); this._disableStatisticsReporter(); } } resume() { if(this._ioctl && this._ioctl.isPaused()) { this._ioctl.resume(); this._enableStatisticsReporter(); } } seek(milliseconds: number) { if(this._mediaDataSource.type === 'm3u8') { // m3u8 处理seek部分 需要去fragment-loader里去处理 this._ioctl && this._ioctl.tsSeek(milliseconds); this.resume(); } else { // flv 处理 seek if(this._mediaInfo == null || !this._mediaInfo.isSeekable()) { return; } const targetSegmentIndex = this._searchSegmentIndexContains(milliseconds); if(targetSegmentIndex === this._currentSegmentIndex) { // intra-segment seeking const segmentInfo = this._mediaInfo.segments ? this._mediaInfo.segments[targetSegmentIndex] : undefined; if(!segmentInfo) { // current segment loading started, but mediainfo hasn't received yet // wait for the metadata loaded, then seek to expected position this._pendingSeekTime = milliseconds; } else { const keyframe = segmentInfo.getNearestKeyframe(milliseconds); this._mediaCodec && this._mediaCodec.seek(keyframe.milliseconds); this._ioctl && this._ioctl.seek(keyframe.fileposition); // Will be resolved in _onRemuxerMediaSegmentArrival() this._pendingResolveSeekPoint = keyframe.milliseconds; } } else { // cross-segment seeking const targetSegmentInfo = this._mediaInfo.segments && this._mediaInfo.segments[targetSegmentIndex] ? this._mediaInfo.segments[targetSegmentIndex] : undefined; if(targetSegmentInfo === undefined) { // target segment hasn't been loaded. We need metadata then seek to expected time this._pendingSeekTime = milliseconds; this._internalAbort(); if(this._mediaCodec) { this._mediaCodec.seek(); this._mediaCodec.insertDiscontinuity(); } this._loadSegment(targetSegmentIndex); // Here we wait for the metadata loaded, then seek to expected position } else { // We have target segment's metadata, direct seek to target position const keyframe = targetSegmentInfo.getNearestKeyframe(milliseconds); this._internalAbort(); if(this._mediaCodec) { this._mediaCodec.seek(); this._mediaCodec.insertDiscontinuity(); this._mediaCodec.resetMediaInfo(); this._mediaCodec.timestampBase = (this._mediaDataSource.segments as Array< FileSegment >)[targetSegmentIndex].timestampBase; } this._loadSegment(targetSegmentIndex, keyframe.fileposition); this._pendingResolveSeekPoint = keyframe.milliseconds; this._reportSegmentMediaInfo(targetSegmentIndex); } } } this._enableStatisticsReporter(); } /** * 根据要跳转的毫秒数和每个Segment的timestampBase的比较, 来查找相应的Segment索引值 * @param milliseconds 跳转的毫秒数 */ _searchSegmentIndexContains(milliseconds: number) { const segments = this._mediaDataSource.segments as Array<FileSegment>; let idx = segments.length - 1; for(let i = 0; i < segments.length; i++) { if(milliseconds < segments[i].timestampBase) { idx = i - 1; break; } } return idx; } /** * 当loader第一次发送过来数据时, 先判断解码器是否能解码数据, 然后初始化解码器, 绑定事件, 否则触发错误 * @param { ArrayBuffer } data flv 是 array m3u8的loader是Uint8Array * @param { Number } byteStart 本次收到的数据的偏移量 * @param { Number } receivedLength 累计的bytelength * @param { Object } extraData m3u8的fragmentLoader所发出来的数据 详见 fragment-loader */ _onInitChunkArrival( data: ArrayBuffer, byteStart: number, extraData: TSExtraData ) { /** * probe功能探测数据返回的结果 */ let probeData = null; /** * 已被处理的数据的长度 */ let consumed = 0; if(byteStart > 0) { // IOController seeked immediately after opened, byteStart > 0 callback may received if(this._mediaCodec) { this._mediaCodec.bindDataSource(this._ioctl); this._mediaCodec.timestampBase = (this._mediaDataSource.segments as Array< FileSegment >)[this._currentSegmentIndex].timestampBase; consumed = this._mediaCodec.parseChunks(data, byteStart, extraData); return consumed; } } probeData = FLVCodec.probe(data); if(probeData.match) { // Always create new FLVDemuxer this._mediaCodec = new FLVCodec(data, this._config); const mds = this._mediaDataSource; if(mds.duration !== undefined && !Number.isNaN(mds.duration)) { this._mediaCodec.overridedDuration = mds.duration; } if(typeof mds.hasAudio === 'boolean') { this._mediaCodec.overridedHasAudio = mds.hasAudio; } if(typeof mds.hasVideo === 'boolean') { this._mediaCodec.overridedHasVideo = mds.hasVideo; } this._mediaCodec.timestampBase = (mds.segments as Array<FileSegment>)[ this._currentSegmentIndex ].timestampBase; this._mediaCodecBindEvents(); // this._remuxer.bindDataSource(this._demuxer.bindDataSource(this._ioctl)); this._mediaCodec.bindDataSource(this._ioctl); consumed = this._mediaCodec.parseChunks(data, byteStart); return consumed; } const tsProbeResult = TSCodec.probe(data); if(tsProbeResult) { this._mediaCodec = new TSCodec( createHLSDefaultConfig(), this.typeSupported, this.navigator ); this._mediaCodecBindEvents(); this._mediaCodec.resetInitSegment( new Uint8Array(extraData.initSegmentData), extraData.audioCodec, extraData.videoCodec, extraData.totalduration ); this._mediaCodec.resetTimeStamp(undefined); // ts的_demuxer.resetTimeStamp 是没有任何作用的 this._mediaCodec.bindDataSource(this._ioctl); this._mediaCodec.parseChunks(new Uint8Array(data), byteStart, extraData); consumed = data.byteLength; // 每个TS文件都为188个字节的整数倍, 所以处理过的数据应为全部数据, 没有余量 return consumed; } probeData = null; Logger.error(this.Tag, `Unsupported media type, this ${this._mediaDataSource.type} stream is not standard format`); Promise.resolve().then(() => { this._internalAbort(); }); this._emitter.emit( Events.DEMUX_ERROR, Errors.FORMAT_UNSUPPORTED, `Unsupported media type, this ${this._mediaDataSource.type} stream is not standard format!` ); consumed = 0; return consumed; } _onMediaInfo(mediaInfo: MediaInfo) { if(this._mediaInfo === null) { // Store first segment's mediainfo as global mediaInfo this._mediaInfo = { ...mediaInfo }; this._mediaInfo.keyframesIndex = null; this._mediaInfo.segments = []; this._mediaInfo.segmentCount = (this._mediaDataSource.segments as Array< FileSegment >).length; Object.setPrototypeOf(this._mediaInfo, MediaInfo.prototype); } const segmentInfo = { ...mediaInfo }; Object.setPrototypeOf(segmentInfo, MediaInfo.prototype); (this._mediaInfo.segments as any)[this._currentSegmentIndex] = segmentInfo; // notify mediaInfo update this._reportSegmentMediaInfo(this._currentSegmentIndex); if(this._pendingSeekTime != null) { Promise.resolve().then(() => { const target = Number(this._pendingSeekTime); this._pendingSeekTime = null; this.seek(target); }); } } _onMetaDataArrived(metadata: Metadata) { this._emitter.emit(Events.METADATA_ARRIVED, metadata); } _onScriptDataArrived(data: any) { this._emitter.emit(Events.SCRIPTDATA_ARRIVED, data); } _onIOSeeked() { this._mediaCodec && this._mediaCodec.insertDiscontinuity(); } _onIOComplete(extraData: number) { const segmentIndex = extraData; const nextSegmentIndex = segmentIndex + 1; if(this._mediaCodec) { if(nextSegmentIndex < (this._mediaDataSource.segments as Array<FileSegment>).length) { this._internalAbort(); this._mediaCodec.flushStashedSamples && this._mediaCodec.flushStashedSamples(); this._loadSegment(nextSegmentIndex); } else { this._mediaCodec.flushStashedSamples && this._mediaCodec.flushStashedSamples(); this._emitter.emit(Events.LOAD_COMPLETE); this._disableStatisticsReporter(); } } } _onIORedirect(redirectedURL: string) { const segmentIndex: number = <number>(this._ioctl as IOController).extraData; (this._mediaDataSource.segments as Array<FileSegment>)[ segmentIndex ].redirectedURL = redirectedURL; } _onIORecoveredEarlyEof() { this._emitter.emit(Events.RECOVERED_EARLY_EOF); } _onIOException(type: string, info: ErrorData) { Logger.error( this.Tag, `IOException: type = ${type}, code = ${info.code}, msg = ${info.reason}` ); this._emitter.emit(Events.IO_ERROR, type, info); this._disableStatisticsReporter(); } _onDemuxException(type: string, info: ErrorData) { Logger.error(this.Tag, `DemuxException: type = ${type}, info = ${info.reason}`); this._emitter.emit(Events.DEMUX_ERROR, type, info); } _onRemuxerInitSegmentArrival(type: string, initSegment: InitSegment) { this._emitter.emit(Events.INIT_SEGMENT, type, initSegment); } _onRemuxerMediaSegmentArrival(type: string, mediaSegment: MediaSegment) { if(this._pendingSeekTime != null) { // Media segments after new-segment cross-seeking should be dropped. return; } this._emitter.emit(Events.MEDIA_SEGMENT, type, mediaSegment); // Resolve pending seekPoint if(this._pendingResolveSeekPoint != null && type === 'video') { const { syncPoints } = mediaSegment.info; let seekpoint = this._pendingResolveSeekPoint; this._pendingResolveSeekPoint = null; // Safari: Pass PTS for recommend_seekpoint if( Browser.safari && syncPoints.length > 0 && syncPoints[0].originalDts === seekpoint ) { seekpoint = syncPoints[0].pts; } // else: use original DTS (keyframe.milliseconds) this._emitter.emit(Events.RECOMMEND_SEEKPOINT, seekpoint); } } _enableStatisticsReporter() { if(this._statisticsReportTimer == null) { this._statisticsReportTimer = GG.setInterval( this._reportStatisticsInfo.bind(this), this._config.statisticsInfoReportInterval ); } } _disableStatisticsReporter() { if(this._statisticsReportTimer) { GG.clearInterval(this._statisticsReportTimer); this._statisticsReportTimer = null; } } _reportSegmentMediaInfo(segmentIndex: number) { const segmentInfo = ((this._mediaInfo as MediaInfo).segments as Array<FileSegment>)[ segmentIndex ]; const exportInfo = { ...segmentInfo }; exportInfo.duration = <number>(this._mediaInfo as MediaInfo).duration; exportInfo.segmentCount = (this._mediaInfo as MediaInfo).segmentCount; delete exportInfo.segments; delete exportInfo.keyframesIndex; this._emitter.emit(Events.MEDIA_INFO, exportInfo); } _reportStatisticsInfo() { const info = Object.create(null); if(this._ioctl) { info.url = this._ioctl.currentURL; info.hasRedirect = this._ioctl.hasRedirect; if(info.hasRedirect) { info.redirectedURL = this._ioctl.currentRedirectedURL; } info.speed = this._ioctl.currentSpeed; info.loaderType = this._ioctl.loaderType; } info.currentSegmentIndex = this._currentSegmentIndex; info.totalSegmentCount = (this._mediaDataSource.segments as Array<FileSegment>).length; this._emitter.emit(Events.STATISTICS_INFO, info); } _onManifestParsed(data: TSManifest) { this._emitter.emit(Events.MANIFEST_PARSED, data); } _mediaCodecBindEvents() { if(!this._mediaCodec) { return; } this._mediaCodec.on(Events.ERROR, (info: ErrorData) => { this._onDemuxException(Errors.TRANSMUXING_ERROR, info); }); this._mediaCodec.on(Events.MEDIA_INFO, (mediaInfo: MediaInfo) => { this._onMediaInfo(mediaInfo); }); this._mediaCodec.on(Events.METADATA_ARRIVED, (metadata: Metadata) => { this._onMetaDataArrived(metadata); }); this._mediaCodec.on(Events.SCRIPTDATA_ARRIVED, (scriptdata: any) => { this._onScriptDataArrived(scriptdata); }); // FLV-demuxer 向上发送SEI信息 this._mediaCodec.on(Events.GET_SEI_INFO, (data: Uint8Array) => { this._emitter.emit(Events.GET_SEI_INFO, data); }); this._mediaCodec.on(Events.INIT_SEGMENT, (type: string, InitSegment: InitSegment) => { this._onRemuxerInitSegmentArrival(type, InitSegment); }); this._mediaCodec.on(Events.MEDIA_SEGMENT, (type: string, MediaSegment: MediaSegment) => { this._onRemuxerMediaSegmentArrival(type, MediaSegment); }); // 上一个fragment解析完毕, 通知loader加载下一个fragment this._mediaCodec.on(Events.LOAD_NEXT_FRAG, () => { if(this._ioctl && this._ioctl.isWorking()) { this._ioctl.loadNextFrag(); } }); } } export default TransmuxingController;