mpegts.js
Version:
HTML5 MPEG2-TS Stream Player
262 lines (221 loc) • 8.07 kB
JavaScript
/*
* Copyright (C) 2016 Bilibili. All Rights Reserved.
*
* @author zheng qian <xqq@xqq.im>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import EventEmitter from 'events';
import PlayerEvents from './player-events';
import {createDefaultConfig} from '../config.js';
import {InvalidArgumentException, IllegalStateException} from '../utils/exception.js';
// Player wrapper for browser's native player (HTMLVideoElement) without MediaSource src.
class NativePlayer {
constructor(mediaDataSource, config) {
this.TAG = 'NativePlayer';
this._type = 'NativePlayer';
this._emitter = new EventEmitter();
this._config = createDefaultConfig();
if (typeof config === 'object') {
Object.assign(this._config, config);
}
let typeLowerCase = mediaDataSource.type.toLowerCase();
if (typeLowerCase === 'mse'
|| typeLowerCase === 'mpegts'
|| typeLowerCase === 'm2ts'
|| typeLowerCase === 'flv') {
throw new InvalidArgumentException('NativePlayer does\'t support mse/mpegts/m2ts/flv MediaDataSource input!');
}
if (mediaDataSource.hasOwnProperty('segments')) {
throw new InvalidArgumentException(`NativePlayer(${mediaDataSource.type}) doesn't support multipart playback!`);
}
this.e = {
onvLoadedMetadata: this._onvLoadedMetadata.bind(this)
};
this._pendingSeekTime = null;
this._statisticsReporter = null;
this._mediaDataSource = mediaDataSource;
this._mediaElement = null;
}
destroy() {
this._emitter.emit(PlayerEvents.DESTROYING);
if (this._mediaElement) {
this.unload();
this.detachMediaElement();
}
this.e = null;
this._mediaDataSource = null;
this._emitter.removeAllListeners();
this._emitter = null;
}
on(event, listener) {
if (event === PlayerEvents.MEDIA_INFO) {
if (this._mediaElement != null && this._mediaElement.readyState !== 0) { // HAVE_NOTHING
Promise.resolve().then(() => {
this._emitter.emit(PlayerEvents.MEDIA_INFO, this.mediaInfo);
});
}
} else if (event === PlayerEvents.STATISTICS_INFO) {
if (this._mediaElement != null && this._mediaElement.readyState !== 0) {
Promise.resolve().then(() => {
this._emitter.emit(PlayerEvents.STATISTICS_INFO, this.statisticsInfo);
});
}
}
this._emitter.addListener(event, listener);
}
off(event, listener) {
this._emitter.removeListener(event, listener);
}
attachMediaElement(mediaElement) {
this._mediaElement = mediaElement;
mediaElement.addEventListener('loadedmetadata', this.e.onvLoadedMetadata);
if (this._pendingSeekTime != null) {
try {
mediaElement.currentTime = this._pendingSeekTime;
this._pendingSeekTime = null;
} catch (e) {
// IE11 may throw InvalidStateError if readyState === 0
// Defer set currentTime operation after loadedmetadata
}
}
}
detachMediaElement() {
if (this._mediaElement) {
this._mediaElement.src = '';
this._mediaElement.removeAttribute('src');
this._mediaElement.removeEventListener('loadedmetadata', this.e.onvLoadedMetadata);
this._mediaElement = null;
}
if (this._statisticsReporter != null) {
window.clearInterval(this._statisticsReporter);
this._statisticsReporter = null;
}
}
load() {
if (!this._mediaElement) {
throw new IllegalStateException('HTMLMediaElement must be attached before load()!');
}
this._mediaElement.src = this._mediaDataSource.url;
if (this._mediaElement.readyState > 0) {
this._mediaElement.currentTime = 0;
}
this._mediaElement.preload = 'auto';
this._mediaElement.load();
this._statisticsReporter = window.setInterval(
this._reportStatisticsInfo.bind(this),
this._config.statisticsInfoReportInterval);
}
unload() {
if (this._mediaElement) {
this._mediaElement.src = '';
this._mediaElement.removeAttribute('src');
}
if (this._statisticsReporter != null) {
window.clearInterval(this._statisticsReporter);
this._statisticsReporter = null;
}
}
play() {
return this._mediaElement.play();
}
pause() {
this._mediaElement.pause();
}
get type() {
return this._type;
}
get buffered() {
return this._mediaElement.buffered;
}
get duration() {
return this._mediaElement.duration;
}
get volume() {
return this._mediaElement.volume;
}
set volume(value) {
this._mediaElement.volume = value;
}
get muted() {
return this._mediaElement.muted;
}
set muted(muted) {
this._mediaElement.muted = muted;
}
get currentTime() {
if (this._mediaElement) {
return this._mediaElement.currentTime;
}
return 0;
}
set currentTime(seconds) {
if (this._mediaElement) {
this._mediaElement.currentTime = seconds;
} else {
this._pendingSeekTime = seconds;
}
}
get mediaInfo() {
let mediaPrefix = (this._mediaElement instanceof HTMLAudioElement) ? 'audio/' : 'video/';
let info = {
mimeType: mediaPrefix + this._mediaDataSource.type
};
if (this._mediaElement) {
info.duration = Math.floor(this._mediaElement.duration * 1000);
if (this._mediaElement instanceof HTMLVideoElement) {
info.width = this._mediaElement.videoWidth;
info.height = this._mediaElement.videoHeight;
}
}
return info;
}
get statisticsInfo() {
let info = {
playerType: this._type,
url: this._mediaDataSource.url
};
if (!(this._mediaElement instanceof HTMLVideoElement)) {
return info;
}
let hasQualityInfo = true;
let decoded = 0;
let dropped = 0;
if (this._mediaElement.getVideoPlaybackQuality) {
let quality = this._mediaElement.getVideoPlaybackQuality();
decoded = quality.totalVideoFrames;
dropped = quality.droppedVideoFrames;
} else if (this._mediaElement.webkitDecodedFrameCount != undefined) {
decoded = this._mediaElement.webkitDecodedFrameCount;
dropped = this._mediaElement.webkitDroppedFrameCount;
} else {
hasQualityInfo = false;
}
if (hasQualityInfo) {
info.decodedFrames = decoded;
info.droppedFrames = dropped;
}
return info;
}
_onvLoadedMetadata(e) {
if (this._pendingSeekTime != null) {
this._mediaElement.currentTime = this._pendingSeekTime;
this._pendingSeekTime = null;
}
this._emitter.emit(PlayerEvents.MEDIA_INFO, this.mediaInfo);
}
_reportStatisticsInfo() {
this._emitter.emit(PlayerEvents.STATISTICS_INFO, this.statisticsInfo);
}
}
export default NativePlayer;