diff --git a/demo/demo.js b/demo/demo.js index ecce849..cdd2dc9 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -65,7 +65,23 @@ function initPlayers() { thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg' }, subtitle: { - url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.vtt' + url: [ + { + url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.vtt', + lang: 'zh-cn', + name: '光', + }, + { + url: 'https://gist.githubusercontent.com/samdutton/ca37f3adaf4e23679957b8083e061177/raw/e19399fbccbc069a2af4266e5120ae6bad62699a/sample.vtt', + lang: 'en-us', + name: 'github', + }, + ], + defaultSubtitle: 7, + type: 'webvtt', + fontSize: '25px', + bottom: '10%', + color: '#b7daff' }, danmaku: { id: '9E2E3368B56CDBB4', diff --git a/src/css/controller.less b/src/css/controller.less index 0076b65..6cc0794 100644 --- a/src/css/controller.less +++ b/src/css/controller.less @@ -408,6 +408,41 @@ display: inline-block; height: 100%; } + .dplayer-subtitles { + display: inline-block; + height: 100%; + .dplayer-subtitles-box { + position: absolute; + right: 0; + bottom: 50px; + transform: scale(0); + width: fit-content; + max-width: 240px; + min-width: 120px; + border-radius: 2px; + background: rgba(28, 28, 28, 0.9); + padding: 7px 0; + transition: all .3s ease-in-out; + overflow: auto; + z-index: 2; + &.dplayer-subtitles-panel { + display: block; + } + &.dplayer-subtitles-box-open { + transform: scale(1); + } + } + .dplayer-subtitles-item { + height: 30px; + padding: 5px 10px; + box-sizing: border-box; + cursor: pointer; + position: relative; + &:hover { + background-color: rgba(255, 255, 255, .1); + } + } + } .dplayer-setting { display: inline-block; height: 100%; diff --git a/src/js/controller.js b/src/js/controller.js index 19ee12f..64db351 100644 --- a/src/js/controller.js +++ b/src/js/controller.js @@ -26,7 +26,12 @@ class Controller { this.initFullButton(); this.initQualityButton(); this.initScreenshotButton(); - this.initSubtitleButton(); + // if subtitle url not array, not init old single subtitle button + if (this.player.options.subtitle) { + if (typeof this.player.options.subtitle.url === 'string') { + this.initSubtitleButton(); + } + } this.initHighlights(); this.initAirplayButton(); this.initChromecastButton(); @@ -364,22 +369,20 @@ class Controller { } initSubtitleButton() { - if (this.player.options.subtitle) { - this.player.events.on('subtitle_show', () => { - this.player.template.subtitleButton.dataset.balloon = this.player.tran('hide-subs'); - this.player.template.subtitleButtonInner.style.opacity = ''; - this.player.user.set('subtitle', 1); - }); - this.player.events.on('subtitle_hide', () => { - this.player.template.subtitleButton.dataset.balloon = this.player.tran('show-subs'); - this.player.template.subtitleButtonInner.style.opacity = '0.4'; - this.player.user.set('subtitle', 0); - }); + this.player.events.on('subtitle_show', () => { + this.player.template.subtitleButton.dataset.balloon = this.player.tran('hide-subs'); + this.player.template.subtitleButtonInner.style.opacity = ''; + this.player.user.set('subtitle', 1); + }); + this.player.events.on('subtitle_hide', () => { + this.player.template.subtitleButton.dataset.balloon = this.player.tran('show-subs'); + this.player.template.subtitleButtonInner.style.opacity = '0.4'; + this.player.user.set('subtitle', 0); + }); - this.player.template.subtitleButton.addEventListener('click', () => { - this.player.subtitle.toggle(); - }); - } + this.player.template.subtitleButton.addEventListener('click', () => { + this.player.subtitle.toggle(); + }); } setAutoHide() { diff --git a/src/js/player.js b/src/js/player.js index 1917d1f..fcbf31f 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -10,6 +10,7 @@ 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'; @@ -60,6 +61,43 @@ class DPlayer { 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, @@ -514,7 +552,12 @@ class DPlayer { 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(); } diff --git a/src/js/subtitle.js b/src/js/subtitle.js index f747100..05ad7a1 100644 --- a/src/js/subtitle.js +++ b/src/js/subtitle.js @@ -17,7 +17,7 @@ class Subtitle { const track = this.video.textTracks[0]; track.oncuechange = () => { - const cue = track.activeCues[0]; + const cue = track.activeCues[track.activeCues.length - 1]; this.container.innerHTML = ''; if (cue) { const template = document.createElement('div'); diff --git a/src/js/subtitles.js b/src/js/subtitles.js new file mode 100644 index 0000000..03fe58d --- /dev/null +++ b/src/js/subtitles.js @@ -0,0 +1,79 @@ +class Subtitles { + constructor(player) { + this.player = player; + + this.player.template.mask.addEventListener('click', () => { + this.hide(); + }); + this.player.template.subtitlesButton.addEventListener('click', () => { + this.adaptiveHeight(); + this.show(); + }); + + const lastItemIndex = this.player.template.subtitlesItem.length - 1; + for (let i = 0; i < lastItemIndex; i++) { + this.player.template.subtitlesItem[i].addEventListener('click', () => { + this.hide(); + if (this.player.options.subtitle.index !== i) { + // clear subtitle show for new subtitle don't have now duration time. If don't, will display last subtitle. + this.player.template.subtitle.innerHTML = `
`; + // update video track src + this.player.template.subtrack.src = this.player.template.subtitlesItem[i].dataset.subtitle; + // update options current subindex for reload (such as changeQuality) + this.player.options.subtitle.index = i; + if (this.player.template.subtitle.classList.contains('dplayer-subtitle-hide')) { + this.subContainerShow(); + } + } + }); + } + this.player.template.subtitlesItem[lastItemIndex].addEventListener('click', () => { + this.hide(); + if (this.player.options.subtitle.index !== lastItemIndex) { + // clear subtitle show for new subtitle don't have now duration time. If don't, will display last subtitle. + this.player.template.subtitle.innerHTML = ``; + // update video track src + this.player.template.subtrack.src = ''; + // update options current subindex for reload (such as changeQuality) + this.player.options.subtitle.index = lastItemIndex; + this.subContainerHide(); + } + }); + } + + subContainerShow() { + this.player.template.subtitle.classList.remove('dplayer-subtitle-hide'); + this.player.events.trigger('subtitle_show'); + } + + subContainerHide() { + this.player.template.subtitle.classList.add('dplayer-subtitle-hide'); + this.player.events.trigger('subtitle_hide'); + } + + hide() { + this.player.template.subtitlesBox.classList.remove('dplayer-subtitles-box-open'); + this.player.template.mask.classList.remove('dplayer-mask-show'); + this.player.controller.disableAutoHide = false; + } + + show() { + this.player.template.subtitlesBox.classList.add('dplayer-subtitles-box-open'); + this.player.template.mask.classList.add('dplayer-mask-show'); + this.player.controller.disableAutoHide = true; + } + + adaptiveHeight() { + const curBoxHeight = this.player.template.subtitlesItem.length * 30 + 14; + const stdMaxHeight = this.player.template.videoWrap.offsetHeight * 0.8; + if (curBoxHeight >= stdMaxHeight - 50) { + this.player.template.subtitlesBox.style.bottom = '8px'; + this.player.template.subtitlesBox.style['max-height'] = stdMaxHeight - 8 + 'px'; + } else { + this.player.template.subtitlesBox.style.bottom = '50px'; + this.player.template.subtitlesBox.style['max-height'] = stdMaxHeight - 50 + 'px'; + } + } +} + +export default Subtitles; diff --git a/src/js/template.js b/src/js/template.js index 4596b68..3f1154b 100644 --- a/src/js/template.js +++ b/src/js/template.js @@ -84,7 +84,11 @@ class Template { this.chromecastButton = this.container.querySelector('.dplayer-chromecast-icon'); this.subtitleButton = this.container.querySelector('.dplayer-subtitle-icon'); this.subtitleButtonInner = this.container.querySelector('.dplayer-subtitle-icon .dplayer-icon-content'); + this.subtitlesButton = this.container.querySelector('.dplayer-subtitles-icon'); + this.subtitlesBox = this.container.querySelector('.dplayer-subtitles-box'); + this.subtitlesItem = this.container.querySelectorAll('.dplayer-subtitles-item'); this.subtitle = this.container.querySelector('.dplayer-subtitle'); + this.subtrack = this.container.querySelector('.dplayer-subtrack'); this.qualityButton = this.container.querySelector('.dplayer-quality-icon'); this.barPreview = this.container.querySelector('.dplayer-bar-preview'); this.barWrap = this.container.querySelector('.dplayer-bar-wrap'); diff --git a/src/template/player.art b/src/template/player.art index 6f01264..4e54151 100644 --- a/src/template/player.art +++ b/src/template/player.art @@ -131,11 +131,29 @@ {{ if options.subtitle }} + {{ if (typeof options.subtitle.url === 'string') }}