123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717 |
- import Promise from 'promise-polyfill';
- import utils from './utils';
- import handleOption from './options';
- import { i18n } from './i18n';
- import Template from './template';
- import Icons from './icons';
- import Danmaku from './danmaku';
- import Events from './events';
- import FullScreen from './fullscreen';
- import User from './user';
- import Subtitle from './subtitle';
- import Subtitles from './subtitles';
- import Bar from './bar';
- import Timer from './timer';
- import Bezel from './bezel';
- import Controller from './controller';
- import Setting from './setting';
- import Comment from './comment';
- import HotKey from './hotkey';
- import ContextMenu from './contextmenu';
- import InfoPanel from './info-panel';
- import tplVideo from '../template/video.art';
- let index = 0;
- const instances = [];
- class DPlayer {
- /**
- * DPlayer constructor function
- *
- * @param {Object} options - See README
- * @constructor
- */
- constructor(options) {
- this.options = handleOption({ preload: options.video.type === 'webtorrent' ? 'none' : 'metadata', ...options });
- if (this.options.video.quality) {
- this.qualityIndex = this.options.video.defaultQuality;
- this.quality = this.options.video.quality[this.options.video.defaultQuality];
- }
- this.tran = new i18n(this.options.lang).tran;
- this.events = new Events();
- this.user = new User(this);
- this.container = this.options.container;
- this.noticeList = {};
- this.container.classList.add('dplayer');
- if (!this.options.danmaku) {
- this.container.classList.add('dplayer-no-danmaku');
- }
- if (this.options.live) {
- this.container.classList.add('dplayer-live');
- } else {
- this.container.classList.remove('dplayer-live');
- }
- if (utils.isMobile) {
- this.container.classList.add('dplayer-mobile');
- }
- this.arrow = this.container.offsetWidth <= 500;
- if (this.arrow) {
- this.container.classList.add('dplayer-arrow');
- }
- // multi subtitles defaultSubtitle add index, off option
- if (this.options.subtitle) {
- if (Array.isArray(this.options.subtitle.url)) {
- const offSubtitle = {
- subtitle: '',
- lang: 'off',
- };
- this.options.subtitle.url.push(offSubtitle);
- if (this.options.subtitle.defaultSubtitle) {
- if (typeof this.options.subtitle.defaultSubtitle === 'string') {
- // defaultSubtitle is string, match in lang then name.
- this.options.subtitle.index = this.options.subtitle.url.findIndex((sub) =>
- /* if (sub.lang === this.options.subtitle.defaultSubtitle) {
- return true;
- } else if (sub.name === this.options.subtitle.defaultSubtitle) {
- return true;
- } else {
- return false;
- } */
- sub.lang === this.options.subtitle.defaultSubtitle ? true : sub.name === this.options.subtitle.defaultSubtitle ? true : false
- );
- } else if (typeof this.options.subtitle.defaultSubtitle === 'number') {
- // defaultSubtitle is int, directly use for index
- this.options.subtitle.index = this.options.subtitle.defaultSubtitle;
- }
- }
- // defaultSubtitle not match or not exist or index bound(when defaultSubtitle is int), try browser language.
- if (this.options.subtitle.index === -1 || !this.options.subtitle.index || this.options.subtitle.index > this.options.subtitle.url.length - 1) {
- this.options.subtitle.index = this.options.subtitle.url.findIndex((sub) => sub.lang === this.options.lang);
- }
- // browser language not match, default off title
- if (this.options.subtitle.index === -1) {
- this.options.subtitle.index = this.options.subtitle.url.length - 1;
- }
- }
- }
- this.template = new Template({
- container: this.container,
- options: this.options,
- index: index,
- tran: this.tran,
- });
- this.video = this.template.video;
- this.bar = new Bar(this.template);
- this.bezel = new Bezel(this.template.bezel);
- this.fullScreen = new FullScreen(this);
- this.controller = new Controller(this);
- if (this.options.danmaku) {
- this.danmaku = new Danmaku({
- player: this,
- container: this.template.danmaku,
- opacity: this.user.get('opacity'),
- callback: () => {
- setTimeout(() => {
- this.template.danmakuLoading.style.display = 'none';
- // autoplay
- if (this.options.autoplay) {
- this.play();
- }
- }, 0);
- },
- error: (msg) => {
- this.notice(msg);
- },
- apiBackend: this.options.apiBackend,
- borderColor: this.options.theme,
- height: this.arrow ? 24 : 30,
- time: () => this.video.currentTime,
- unlimited: this.user.get('unlimited'),
- api: {
- id: this.options.danmaku.id,
- address: this.options.danmaku.api,
- token: this.options.danmaku.token,
- maximum: this.options.danmaku.maximum,
- addition: this.options.danmaku.addition,
- user: this.options.danmaku.user,
- speedRate: this.options.danmaku.speedRate,
- },
- events: this.events,
- tran: (msg) => this.tran(msg),
- });
- this.comment = new Comment(this);
- }
- this.setting = new Setting(this);
- this.plugins = {};
- this.docClickFun = () => {
- this.focus = false;
- };
- this.containerClickFun = () => {
- this.focus = true;
- };
- document.addEventListener('click', this.docClickFun, true);
- this.container.addEventListener('click', this.containerClickFun, true);
- this.paused = true;
- this.timer = new Timer(this);
- this.hotkey = new HotKey(this);
- this.contextmenu = new ContextMenu(this);
- this.initVideo(this.video, (this.quality && this.quality.type) || this.options.video.type);
- this.infoPanel = new InfoPanel(this);
- if (!this.danmaku && this.options.autoplay) {
- this.play();
- }
- this.moveBar = false;
- index++;
- instances.push(this);
- }
- /**
- * Seek video
- */
- seek(time) {
- time = Math.max(time, 0);
- if (this.video.duration) {
- time = Math.min(time, this.video.duration);
- }
- if (this.video.currentTime < time) {
- this.notice(`${this.tran('ff').replace('%s', (time - this.video.currentTime).toFixed(0))}`);
- } else if (this.video.currentTime > time) {
- this.notice(`${this.tran('rew').replace('%s', (this.video.currentTime - time).toFixed(0))}`);
- }
- this.video.currentTime = time;
- if (this.danmaku) {
- this.danmaku.seek();
- }
- this.bar.set('played', time / this.video.duration, 'width');
- this.template.ptime.innerHTML = utils.secondToTime(time);
- }
- /**
- * Play video
- */
- play(fromNative) {
- this.paused = false;
- if (this.video.paused && !utils.isMobile) {
- this.bezel.switch(Icons.play);
- }
- this.template.playButton.innerHTML = Icons.pause;
- this.template.mobilePlayButton.innerHTML = Icons.pause;
- if (!fromNative) {
- const playedPromise = Promise.resolve(this.video.play());
- playedPromise
- .catch(() => {
- this.pause();
- })
- .then(() => {});
- }
- this.timer.enable('loading');
- this.container.classList.remove('dplayer-paused');
- this.container.classList.add('dplayer-playing');
- if (this.danmaku) {
- this.danmaku.play();
- }
- if (this.options.mutex) {
- for (let i = 0; i < instances.length; i++) {
- if (this !== instances[i]) {
- instances[i].pause();
- }
- }
- }
- }
- /**
- * Pause video
- */
- pause(fromNative) {
- this.paused = true;
- this.container.classList.remove('dplayer-loading');
- if (!this.video.paused && !utils.isMobile) {
- this.bezel.switch(Icons.pause);
- }
- this.template.playButton.innerHTML = Icons.play;
- this.template.mobilePlayButton.innerHTML = Icons.play;
- if (!fromNative) {
- this.video.pause();
- }
- this.timer.disable('loading');
- this.container.classList.remove('dplayer-playing');
- this.container.classList.add('dplayer-paused');
- if (this.danmaku) {
- this.danmaku.pause();
- }
- }
- switchVolumeIcon() {
- if (this.volume() >= 0.95) {
- this.template.volumeIcon.innerHTML = Icons.volumeUp;
- } else if (this.volume() > 0) {
- this.template.volumeIcon.innerHTML = Icons.volumeDown;
- } else {
- this.template.volumeIcon.innerHTML = Icons.volumeOff;
- }
- }
- /**
- * Set volume
- */
- volume(percentage, nostorage, nonotice) {
- percentage = parseFloat(percentage);
- if (!isNaN(percentage)) {
- percentage = Math.max(percentage, 0);
- percentage = Math.min(percentage, 1);
- this.bar.set('volume', percentage, 'width');
- const formatPercentage = `${(percentage * 100).toFixed(0)}%`;
- this.template.volumeBarWrapWrap.dataset.balloon = formatPercentage;
- if (!nostorage) {
- this.user.set('volume', percentage);
- }
- if (!nonotice) {
- this.notice(`${this.tran('volume')} ${(percentage * 100).toFixed(0)}%`, undefined, undefined, 'volume');
- }
- this.video.volume = percentage;
- if (this.video.muted) {
- this.video.muted = false;
- }
- this.switchVolumeIcon();
- }
- return this.video.volume;
- }
- /**
- * Toggle between play and pause
- */
- toggle() {
- if (this.video.paused) {
- this.play();
- } else {
- this.pause();
- }
- }
- /**
- * attach event
- */
- on(name, callback) {
- this.events.on(name, callback);
- }
- /**
- * Switch to a new video
- *
- * @param {Object} video - new video info
- * @param {Object} danmaku - new danmaku info
- */
- switchVideo(video, danmakuAPI) {
- this.pause();
- this.video.poster = video.pic ? video.pic : '';
- this.video.src = video.url;
- this.initMSE(this.video, video.type || 'auto');
- if (danmakuAPI) {
- this.template.danmakuLoading.style.display = 'block';
- this.bar.set('played', 0, 'width');
- this.bar.set('loaded', 0, 'width');
- this.template.ptime.innerHTML = '00:00';
- this.template.danmaku.innerHTML = '';
- if (this.danmaku) {
- this.danmaku.reload({
- id: danmakuAPI.id,
- address: danmakuAPI.api,
- token: danmakuAPI.token,
- maximum: danmakuAPI.maximum,
- addition: danmakuAPI.addition,
- user: danmakuAPI.user,
- });
- }
- }
- }
- initMSE(video, type) {
- this.type = type;
- if (this.options.video.customType && this.options.video.customType[type]) {
- if (Object.prototype.toString.call(this.options.video.customType[type]) === '[object Function]') {
- this.options.video.customType[type](this.video, this);
- } else {
- console.error(`Illegal customType: ${type}`);
- }
- } else {
- if (this.type === 'auto') {
- if (/m3u8(#|\?|$)/i.exec(video.src)) {
- this.type = 'hls';
- } else if (/.flv(#|\?|$)/i.exec(video.src)) {
- this.type = 'flv';
- } else if (/.mpd(#|\?|$)/i.exec(video.src)) {
- this.type = 'dash';
- } else {
- this.type = 'normal';
- }
- }
- if (this.type === 'hls' && (video.canPlayType('application/x-mpegURL') || video.canPlayType('application/vnd.apple.mpegURL'))) {
- this.type = 'normal';
- }
- switch (this.type) {
- // https://github.com/video-dev/hls.js
- case 'hls':
- if (window.Hls) {
- if (window.Hls.isSupported()) {
- const options = this.options.pluginOptions.hls;
- const hls = new window.Hls(options);
- this.plugins.hls = hls;
- hls.loadSource(video.src);
- hls.attachMedia(video);
- this.events.on('destroy', () => {
- hls.destroy();
- delete this.plugins.hls;
- });
- } else {
- this.notice('Error: Hls is not supported.');
- }
- } else {
- this.notice("Error: Can't find Hls.");
- }
- break;
- // https://github.com/Bilibili/flv.js
- case 'flv':
- if (window.flvjs) {
- if (window.flvjs.isSupported()) {
- const flvPlayer = window.flvjs.createPlayer(
- Object.assign(this.options.pluginOptions.flv.mediaDataSource || {}, {
- type: 'flv',
- url: video.src,
- }),
- this.options.pluginOptions.flv.config
- );
- this.plugins.flvjs = flvPlayer;
- flvPlayer.attachMediaElement(video);
- flvPlayer.load();
- this.events.on('destroy', () => {
- flvPlayer.unload();
- flvPlayer.detachMediaElement();
- flvPlayer.destroy();
- delete this.plugins.flvjs;
- });
- } else {
- this.notice('Error: flvjs is not supported.');
- }
- } else {
- this.notice("Error: Can't find flvjs.");
- }
- break;
- // https://github.com/Dash-Industry-Forum/dash.js
- case 'dash':
- if (window.dashjs) {
- const dashjsPlayer = window.dashjs.MediaPlayer().create();
- dashjsPlayer.initialize(video, video.src, false, 0);
- const options = this.options.pluginOptions.dash;
- dashjsPlayer.updateSettings(options);
- this.plugins.dash = dashjsPlayer;
- this.events.on('destroy', () => {
- window.dashjs.MediaPlayer().reset();
- delete this.plugins.dash;
- });
- } else {
- this.notice("Error: Can't find dashjs.");
- }
- break;
- // https://github.com/webtorrent/webtorrent
- case 'webtorrent':
- if (window.WebTorrent) {
- if (window.WebTorrent.WEBRTC_SUPPORT) {
- this.container.classList.add('dplayer-loading');
- const options = this.options.pluginOptions.webtorrent;
- const client = new window.WebTorrent(options);
- this.plugins.webtorrent = client;
- const torrentId = video.src;
- video.src = '';
- video.preload = 'metadata';
- video.addEventListener('durationchange', () => this.container.classList.remove('dplayer-loading'), { once: true });
- client.add(torrentId, (torrent) => {
- const file = torrent.files.find((file) => file.name.endsWith('.mp4'));
- file.renderTo(this.video, {
- autoplay: this.options.autoplay,
- controls: false,
- });
- });
- this.events.on('destroy', () => {
- client.remove(torrentId);
- client.destroy();
- delete this.plugins.webtorrent;
- });
- } else {
- this.notice('Error: Webtorrent is not supported.');
- }
- } else {
- this.notice("Error: Can't find Webtorrent.");
- }
- break;
- }
- }
- }
- initVideo(video, type) {
- this.initMSE(video, type);
- /**
- * video events
- */
- // show video time: the metadata has loaded or changed
- this.on('durationchange', () => {
- // compatibility: Android browsers will output 1 or Infinity at first
- if (video.duration !== 1 && video.duration !== Infinity) {
- this.template.dtime.innerHTML = utils.secondToTime(video.duration);
- }
- });
- // show video loaded bar: to inform interested parties of progress downloading the media
- this.on('progress', () => {
- const percentage = video.buffered.length ? video.buffered.end(video.buffered.length - 1) / video.duration : 0;
- this.bar.set('loaded', percentage, 'width');
- });
- // video download error: an error occurs
- this.on('error', () => {
- if (!this.video.error) {
- // Not a video load error, may be poster load failed, see #307
- return;
- }
- this.tran && this.notice && this.type !== 'webtorrent' && this.notice(this.tran('video-failed'));
- });
- // video end
- this.on('ended', () => {
- this.bar.set('played', 1, 'width');
- if (!this.setting.loop) {
- this.pause();
- } else {
- this.seek(0);
- this.play();
- }
- if (this.danmaku) {
- this.danmaku.danIndex = 0;
- }
- });
- this.on('play', () => {
- if (this.paused) {
- this.play(true);
- }
- });
- this.on('pause', () => {
- if (!this.paused) {
- this.pause(true);
- }
- });
- this.on('timeupdate', () => {
- if (!this.moveBar) {
- this.bar.set('played', this.video.currentTime / this.video.duration, 'width');
- }
- const currentTime = utils.secondToTime(this.video.currentTime);
- if (this.template.ptime.innerHTML !== currentTime) {
- this.template.ptime.innerHTML = currentTime;
- }
- });
- for (let i = 0; i < this.events.videoEvents.length; i++) {
- video.addEventListener(this.events.videoEvents[i], (e) => {
- this.events.trigger(this.events.videoEvents[i], e);
- });
- }
- this.volume(this.user.get('volume'), true, true);
- if (this.options.subtitle) {
- // init old single subtitle function(sub show and style)
- this.subtitle = new Subtitle(this.template.subtitle, this.video, this.options.subtitle, this.events);
- // init multi subtitles function(sub update)
- if (Array.isArray(this.options.subtitle.url)) {
- this.subtitles = new Subtitles(this);
- }
- if (!this.user.get('subtitle')) {
- this.subtitle.hide();
- }
- }
- }
- switchQuality(index) {
- index = typeof index === 'string' ? parseInt(index) : index;
- if (this.qualityIndex === index || this.switchingQuality) {
- return;
- } else {
- this.prevIndex = this.qualityIndex;
- this.qualityIndex = index;
- }
- this.switchingQuality = true;
- this.quality = this.options.video.quality[index];
- this.template.qualityButton.innerHTML = this.quality.name;
- const paused = this.video.paused;
- this.video.pause();
- const videoHTML = tplVideo({
- current: false,
- pic: null,
- screenshot: this.options.screenshot,
- preload: 'auto',
- url: this.quality.url,
- subtitle: this.options.subtitle,
- });
- const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;
- this.template.videoWrap.insertBefore(videoEle, this.template.videoWrap.getElementsByTagName('div')[0]);
- this.prevVideo = this.video;
- this.video = videoEle;
- this.initVideo(this.video, this.quality.type || this.options.video.type);
- this.seek(this.prevVideo.currentTime);
- this.notice(`${this.tran('switching-quality').replace('%q', this.quality.name)}`, -1, undefined, 'switch-quality');
- this.events.trigger('quality_start', this.quality);
- this.on('canplay', () => {
- if (this.prevVideo) {
- if (this.video.currentTime !== this.prevVideo.currentTime) {
- this.seek(this.prevVideo.currentTime);
- return;
- }
- this.template.videoWrap.removeChild(this.prevVideo);
- this.video.classList.add('dplayer-video-current');
- if (!paused) {
- this.video.play();
- }
- this.prevVideo = null;
- this.notice(`${this.tran('switched-quality').replace('%q', this.quality.name)}`, undefined, undefined, 'switch-quality');
- this.switchingQuality = false;
- this.events.trigger('quality_end');
- }
- });
- this.on('error', () => {
- if (!this.video.error) {
- return;
- }
- if (this.prevVideo) {
- this.template.videoWrap.removeChild(this.video);
- this.video = this.prevVideo;
- if (!paused) {
- this.video.play();
- }
- this.qualityIndex = this.prevIndex;
- this.quality = this.options.video.quality[this.qualityIndex];
- this.noticeTime = setTimeout(() => {
- this.template.notice.style.opacity = 0;
- this.events.trigger('notice_hide');
- }, 3000);
- this.prevVideo = null;
- this.switchingQuality = false;
- }
- });
- }
- notice(text, time = 2000, opacity = 0.8, id) {
- let oldNoticeEle;
- if (id) {
- oldNoticeEle = document.getElementById(`dplayer-notice-${id}`);
- if (oldNoticeEle) {
- oldNoticeEle.innerHTML = text;
- }
- if (this.noticeList[id]) {
- clearTimeout(this.noticeList[id]);
- this.noticeList[id] = null;
- }
- }
- if (!oldNoticeEle) {
- const notice = Template.NewNotice(text, opacity, id);
- this.template.noticeList.appendChild(notice);
- oldNoticeEle = notice;
- }
- this.events.trigger('notice_show', oldNoticeEle);
- if (time > 0) {
- this.noticeList[id] = setTimeout(
- (function (e, dp) {
- return () => {
- e.addEventListener('animationend', () => {
- dp.template.noticeList.removeChild(e);
- });
- e.classList.add('remove-notice');
- dp.events.trigger('notice_hide');
- dp.noticeList[id] = null;
- };
- })(oldNoticeEle, this),
- time
- );
- }
- }
- resize() {
- if (this.danmaku) {
- this.danmaku.resize();
- }
- if (this.controller.thumbnails) {
- this.controller.thumbnails.resize(160, (this.video.videoHeight / this.video.videoWidth) * 160, this.template.barWrap.offsetWidth);
- }
- this.events.trigger('resize');
- }
- speed(rate) {
- this.video.playbackRate = rate;
- }
- destroy() {
- instances.splice(instances.indexOf(this), 1);
- this.pause();
- document.removeEventListener('click', this.docClickFun, true);
- this.container.removeEventListener('click', this.containerClickFun, true);
- this.fullScreen.destroy();
- this.hotkey.destroy();
- this.contextmenu.destroy();
- this.controller.destroy();
- this.timer.destroy();
- this.video.src = '';
- this.container.innerHTML = '';
- this.events.trigger('destroy');
- }
- static get version() {
- /* global DPLAYER_VERSION */
- return DPLAYER_VERSION;
- }
- }
- export default DPlayer;
|