Ver código fonte

Merge branch 'multisubtitle' of github.com:jiliangsoft/DPlayer into jiliangsoft-multisubtitle

DIYgod 2 anos atrás
pai
commit
2b6b46e5f6

+ 17 - 1
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',

+ 35 - 0
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%;

+ 19 - 16
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() {

+ 43 - 0
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();
             }

+ 1 - 1
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');

+ 79 - 0
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 = `<p></p>`;
+                    // 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 = `<p></p>`;
+                // 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;

+ 4 - 0
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');

+ 18 - 0
src/template/player.art

@@ -131,11 +131,29 @@
             </button>
         </div>
         {{ if options.subtitle }}
+        {{ if (typeof options.subtitle.url === 'string') }}
         <div class="dplayer-subtitle-btn">
             <button class="dplayer-icon dplayer-subtitle-icon" data-balloon="{{ tran('hide-subs') }}" data-balloon-pos="up">
                 <span class="dplayer-icon-content">{{@ icons.subtitle }}</span>
             </button>
         </div>
+        {{ else }}
+        <div class="dplayer-subtitles">
+            <button class="dplayer-icon dplayer-subtitles-icon" data-balloon="{{ tran('Subtitle') }}" data-balloon-pos="up">
+                <span class="dplayer-icon-content">{{@ icons.subtitle }}</span>
+            </button>
+            <div class="dplayer-subtitles-box">
+                <div class="dplayer-subtitles-panel">
+                    {{ each options.subtitle.url }}
+                        <div class="dplayer-subtitles-item" data-subtitle="{{ $value.url }}">
+                            <!-- if lang, show tran(lang). if lang and name, show name + (tran(lang)). if name, show name. off option use lang for translation. -->
+                            <span class="dplayer-label">{{ $value.lang ? $value.name ?  $value.name+' ('+tran($value.lang)+')' : tran($value.lang) : $value.name }}</span>
+                        </div>
+                    {{ /each }}
+                </div>
+            </div>
+        </div>
+        {{ /if }}
         {{ /if }}
         <div class="dplayer-setting">
             <button class="dplayer-icon dplayer-setting-icon" data-balloon="{{ tran('setting') }}" data-balloon-pos="up">

+ 1 - 1
src/template/video.art

@@ -17,6 +17,6 @@
     <source src="{{ url }}">
     {{ /if}}
     {{ if enableSubtitle }}
-    <track kind="metadata" default src="{{ subtitle.url }}"></track>
+    <track class="dplayer-subtrack" kind="metadata" default src="{{ typeof subtitle.url === 'string' ? subtitle.url : subtitle.url[subtitle.index].url }}"></track>
     {{ /if }}
 </video>