浏览代码

Quality switching; New option: logo, contextmenu

DIYgod 8 年之前
父节点
当前提交
83d795d983
共有 10 个文件被更改,包括 380 次插入115 次删除
  1. 66 12
      demo/index.html
  2. 0 0
      dist/DPlayer.min.css
  3. 0 0
      dist/DPlayer.min.js
  4. 0 0
      dist/DPlayer.min.js.map
  5. 151 85
      src/DPlayer.js
  6. 91 2
      src/DPlayer.scss
  7. 41 9
      src/html.js
  8. 6 2
      src/i18n.js
  9. 19 1
      src/option.js
  10. 6 4
      src/video.js

+ 66 - 12
demo/index.html

@@ -54,14 +54,16 @@
     <h3>Normal</h3>
     <div class="dplayer" id="dplayer1"></div>
     <button onclick="switchDPlayer()">Switch Video</button>
-    <h3>Bilibili video and danmaku support</h3>
-    <div class="dplayer" id="dplayer2"></div>
+    <h3>Quality switching</h3>
+    <div class="dplayer" id="dplayer6"></div>
     <h3>Live Video (HTTP Live Streaming, M3U8 format) support</h3>
     <div class="dplayer" id="dplayer3"></div>
     <h3>FLV format support</h3>
     <div class="dplayer" id="dplayer4"></div>
-    <h3>Segmented videos</h3>
-    <div class="dplayer" id="dplayer5"></div>
+    <h3>Bilibili video and danmaku support</h3>
+    <div class="dplayer" id="dplayer2"></div>
+    <!--<h3>Segmented videos</h3>
+    <div class="dplayer" id="dplayer5"></div>-->
 </div>
 <script src="https://rawgit.com/mrdoob/stats.js/master/build/stats.min.js"></script>
 <script src="../plugin/flv.min.js"></script>
@@ -76,6 +78,7 @@
         loop: true,
         screenshot: true,
         hotkey: true,
+        logo: 'http://devtest.qiniudn.com/DPlayer.png',
         video: {
             url: 'http://devtest.qiniudn.com/若能绽放光芒.mp4',
             pic: 'http://devtest.qiniudn.com/若能绽放光芒.png',
@@ -87,7 +90,25 @@
             token: 'tokendemo',
             maximum: 3000,
             user: 'DIYgod的女粉'
-        }
+        },
+        contextmenu: [
+            {
+                text: '关于作者',
+                link: 'http://diygod.me'
+            },
+            {
+                text: '播放器意见反馈',
+                link: 'https://github.com/DIYgod/DPlayer/issues'
+            },
+            {
+                text: '关于 DPlayer 播放器',
+                link: 'https://github.com/DIYgod/DPlayer'
+            },
+            {
+                text: '自定义右键菜单demo',
+                link: 'https://github.com/DIYgod/DPlayer'
+            },
+        ]
     });
     function switchDPlayer() {
         if (dp1.option.danmaku.id !== '5rGf5Y2X55qu6Z2p') {
@@ -125,6 +146,7 @@
         theme: '#FADFA3',
         loop: true,
         screenshot: false,
+        logo: 'http://devtest.qiniudn.com/DPlayer.png',
         video: {
             url: 'https://api.prprpr.me/dplayer/video/bilibili?aid=4045652',
             pic: 'http://devtest.qiniudn.com/微小微-江南皮革厂倒闭了.jpg',
@@ -147,6 +169,7 @@
         loop: true,
         screenshot: true,
         hotkey: true,
+        logo: 'http://devtest.qiniudn.com/DPlayer.png',
         video: {
             url: 'http://devtest.qiniudn.com/若能绽放光芒5.m3u8',
             pic: 'http://devtest.qiniudn.com/若能绽放光芒.png',
@@ -168,6 +191,7 @@
         loop: true,
         screenshot: true,
         hotkey: true,
+        logo: 'http://devtest.qiniudn.com/DPlayer.png',
         video: {
             url: 'http://devtest.qiniudn.com/【微小微】玖月奇迹-踩踩踩.flv',
             pic: 'http://devtest.qiniudn.com/【微小微】玖月奇迹-踩踩踩.jpg',
@@ -182,24 +206,54 @@
     });
 
     // Segmented videos
-    var dp5 = new DPlayer({
-        element: document.getElementById('dplayer5'),
+    // var dp5 = new DPlayer({
+    //     element: document.getElementById('dplayer5'),
+    //     autoplay: false,
+    //     theme: '#FADFA3',
+    //     loop: true,
+    //     screenshot: true,
+    //     hotkey: true,
+    //     preload: 'metadata',
+    //     video: {
+    //         url: ['http://devtest.qiniudn.com/若能绽放光芒0.mp4', 'http://devtest.qiniudn.com/若能绽放光芒1.mp4', 'http://devtest.qiniudn.com/若能绽放光芒2.mp4', 'http://devtest.qiniudn.com/若能绽放光芒3.mp4', 'http://devtest.qiniudn.com/若能绽放光芒4.mp4', 'http://devtest.qiniudn.com/若能绽放光芒5.mp4', 'http://devtest.qiniudn.com/若能绽放光芒6.mp4', 'http://devtest.qiniudn.com/若能绽放光芒7.mp4', 'http://devtest.qiniudn.com/若能绽放光芒8.mp4'],
+    //         pic: 'http://devtest.qiniudn.com/若能绽放光芒.png',
+    //         type: 'normal',
+    //     },
+    //     danmaku: {
+    //         id: '9E2E3368B56CDBB40',
+    //         api: 'https://api.prprpr.me/dplayer/',
+    //         token: 'tokendemo',
+    //         maximum: 3000
+    //     }
+    // });
+
+    // Quality switching
+    var dp6 = new DPlayer({
+        element: document.getElementById('dplayer6'),
         autoplay: false,
         theme: '#FADFA3',
         loop: true,
         screenshot: true,
         hotkey: true,
-        preload: 'metadata',
+        logo: 'http://devtest.qiniudn.com/DPlayer.png',
         video: {
-            url: ['http://devtest.qiniudn.com/若能绽放光芒0.mp4', 'http://devtest.qiniudn.com/若能绽放光芒1.mp4', 'http://devtest.qiniudn.com/若能绽放光芒2.mp4', 'http://devtest.qiniudn.com/若能绽放光芒3.mp4', 'http://devtest.qiniudn.com/若能绽放光芒4.mp4', 'http://devtest.qiniudn.com/若能绽放光芒5.mp4', 'http://devtest.qiniudn.com/若能绽放光芒6.mp4', 'http://devtest.qiniudn.com/若能绽放光芒7.mp4', 'http://devtest.qiniudn.com/若能绽放光芒8.mp4'],
+            quality: [{
+                name: '高清',
+                url: 'http://devtest.qiniudn.com/若能绽放光芒.mp4?1'
+            }, {
+                name: '超清',
+                url: 'http://devtest.qiniudn.com/若能绽放光芒.mp4?2'
+            }],
+            defaultQuality: 0,
             pic: 'http://devtest.qiniudn.com/若能绽放光芒.png',
-            type: 'normal',
+            type: 'auto',
         },
         danmaku: {
-            id: '9E2E3368B56CDBB40',
+            id: '9E2E3368B56CDBB4',
             api: 'https://api.prprpr.me/dplayer/',
             token: 'tokendemo',
-            maximum: 3000
+            maximum: 3000,
+            user: 'DIYgod的女粉'
         }
     });
 

文件差异内容过多而无法显示
+ 0 - 0
dist/DPlayer.min.css


文件差异内容过多而无法显示
+ 0 - 0
dist/DPlayer.min.js


文件差异内容过多而无法显示
+ 0 - 0
dist/DPlayer.min.js.map


+ 151 - 85
src/DPlayer.js

@@ -22,7 +22,12 @@ class DPlayer {
     constructor (option) {
         this.option = handleOption(option);
 
-        const tran = new i18n(this.option.lang).tran;
+        if (this.option.video.quality) {
+            this.qualityIndex = this.option.video.defaultQuality;
+            this.quality = this.option.video.quality[this.option.video.defaultQuality];
+        }
+
+        this.tran = new i18n(this.option.lang).tran;
 
         /**
          * Update progress bar, including loading progress bar and play progress bar
@@ -57,7 +62,7 @@ class DPlayer {
             this.element.classList.add('dplayer-mobile');
         }
 
-        this.element.innerHTML = html.main(option, index, tran);
+        this.element.innerHTML = html.main(option, index, this.tran);
 
         // arrow style
         this.arrow = this.element.offsetWidth <= 500;
@@ -68,7 +73,7 @@ class DPlayer {
         }
 
         // get this video manager
-        this.video = new Video(this.element.getElementsByClassName('dplayer-video'));
+        this.video = new Video(this.element.getElementsByClassName('dplayer-video-current'));
 
         // Support HTTP Live Streaming
         let enablehls;
@@ -88,7 +93,7 @@ class DPlayer {
             hls.on(Hls.Events.MEDIA_ATTACHED, () => {
                 hls.loadSource(this.option.video.url);
                 hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
-                    console.log("manifest loaded, found " + data.levels.length + " quality level");
+                    this.notice("manifest loaded, found " + data.levels.length + " quality level");
                 });
             });
         }
@@ -155,18 +160,6 @@ class DPlayer {
         const pbarTimeTips = this.element.getElementsByClassName('dplayer-bar-time')[0];
         let barWidth;
 
-        if (this.option.danmaku) {
-            this.video.on('all', 'seeking', () => {
-                for (let i = 0; i < this.dan.length; i++) {
-                    if (this.dan[i].time >= this.video.currentTime()) {
-                        this.danIndex = i;
-                        return;
-                    }
-                    this.danIndex = this.dan.length;
-                }
-            });
-        }
-
         let lastPlayPos = 0;
         let currentPlayPos = 0;
         let bufferingDetected = false;
@@ -337,7 +330,7 @@ class DPlayer {
          * setting
          */
         this.danOpacity = localStorage.getItem('DPlayer-opacity') || 0.7;
-        const settingHTML = html.setting(tran);
+        const settingHTML = html.setting(this.tran);
 
         // toggle setting box
         const settingIcon = this.element.getElementsByClassName('dplayer-setting-icon')[0];
@@ -368,7 +361,7 @@ class DPlayer {
             openSetting();
         });
 
-        let loop = this.option.loop;
+        this.loop = this.option.loop;
         const danContainer = this.element.getElementsByClassName('dplayer-danmaku')[0];
         let showdan = true;
         const settingEvent = () => {
@@ -376,15 +369,15 @@ class DPlayer {
             const loopEle = this.element.getElementsByClassName('dplayer-setting-loop')[0];
             const loopToggle = loopEle.getElementsByClassName('dplayer-toggle-setting-input')[0];
 
-            loopToggle.checked = loop;
+            loopToggle.checked = this.loop;
 
             loopEle.addEventListener('click', () => {
                 loopToggle.checked = !loopToggle.checked;
                 if (loopToggle.checked) {
-                    loop = true;
+                    this.loop = true;
                 }
                 else {
-                    loop = false;
+                    this.loop = false;
                 }
                 closeSetting();
             });
@@ -497,66 +490,7 @@ class DPlayer {
         };
         settingEvent();
 
-
-        /**
-         * video events
-         */
-        // show video time: the metadata has loaded or changed
-        this.video.on('all', 'durationchange', (i, video) => {
-            if (video.duration !== 1) {           // compatibility: Android browsers will output 1 at first
-                this.element.getElementsByClassName('dplayer-dtime')[0].innerHTML = utils.secondToTime(this.video.duration);
-            }
-        });
-
-        // show video loaded bar: to inform interested parties of progress downloading the media
-        this.video.on('current', 'progress', (i, video) => {
-            const percentage = video.buffered.length ? video.buffered.end(video.buffered.length - 1) / video.duration : 0;
-            this.updateBar('loaded', percentage, 'width');
-        });
-
-        // video download error: an error occurs
-        this.video.on('all', 'error', () => {
-            this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = `Error happens ╥﹏╥`;
-            this.trigger('pause');
-        });
-
-        // video can play: enough data is available that the media can be played
-        this.video.on('current', 'canplay', () => {
-            this.trigger('canplay');
-        });
-
-        // music end
-        this.ended = false;
-        this.video.on('all', 'ended', (i) => {
-            if (i === this.video.videos.length - 1) {
-                this.updateBar('played', 1, 'width');
-                console.log(loop);
-                if (!loop) {
-                    this.ended = true;
-                    this.pause();
-                    this.trigger('ended');
-                }
-                else {
-                    this.video.switch(0);
-                    this.video.play();
-                }
-            }
-        });
-
-        this.video.on('current', 'play', () => {
-            if (this.paused) {
-                this.play();
-            }
-        });
-
-        this.video.on('current', 'pause', () => {
-            if (!this.paused) {
-                this.pause();
-            }
-        });
-
-        // control volume
-        this.video.attr('volume', parseInt(this.element.getElementsByClassName('dplayer-volume-bar-inner')[0].style.width) / 100);
+        this.initVideo();
 
         // set duration time
         if (this.video.duration !== 1) {           // compatibility: Android browsers will output 1 at first
@@ -609,7 +543,7 @@ class DPlayer {
 
             // text can't be empty
             if (!commentInput.value.replace(/^\s+|\s+$/g, '')) {
-                alert(tran('Please input danmaku!'));
+                this.notice(this.tran('Please input danmaku content!'));
                 return;
             }
 
@@ -856,6 +790,18 @@ class DPlayer {
             });
         });
 
+        /**
+         * Switch quality
+         */
+        if (this.option.video.quality) {
+            this.element.getElementsByClassName('dplayer-quality-list')[0].addEventListener('click', (e) => {
+                if (e.target.classList.contains('dplayer-quality-item')) {
+                    this.switchQuality(e.target.dataset.index);
+                    this.element.getElementsByClassName('dplayer-quality-icon')[0].innerHTML = this.option.video.quality[this.qualityIndex].name;
+                }
+            });
+        }
+
         /**
          * Screenshot
          */
@@ -964,10 +910,10 @@ class DPlayer {
             ++readCount;
             if (err) {
                 if (err.response) {
-                    console.log(err.response.msg);                    
+                    this.notice(err.response.msg);                    
                 }
                 else {
-                    console.log('Request was unsuccessful: ' + err.status);                    
+                    this.notice('Request was unsuccessful: ' + err.status);                    
                 }
                 results[i] = [];
             }
@@ -1128,7 +1074,7 @@ class DPlayer {
             this.updateBar('loaded', 0, 'width');
             this.element.getElementsByClassName('dplayer-ptime')[0].innerHTML = '00:00';
             this.element.getElementsByClassName('dplayer-danmaku')[0].innerHTML = `<div class="dplayer-danmaku-item  dplayer-danmaku-item--demo"></div>`;
-            this.danTunnel = {
+            this.danTuel = {
                 right: {},
                 top: {},
                 bottom: {}
@@ -1139,6 +1085,111 @@ class DPlayer {
         }
     }
 
+    initVideo () {
+        if (this.option.danmaku) {
+            this.video.on('all', 'seeking', () => {
+                for (let i = 0; i < this.dan.length; i++) {
+                    if (this.dan[i].time >= this.video.currentTime()) {
+                        this.danIndex = i;
+                        return;
+                    }
+                    this.danIndex = this.dan.length;
+                }
+            });
+        }
+
+
+        /**
+         * video events
+         */
+        // show video time: the metadata has loaded or changed
+        this.video.on('all', 'durationchange', (i, video) => {
+            if (video.duration !== 1) {           // compatibility: Android browsers will output 1 at first
+                this.element.getElementsByClassName('dplayer-dtime')[0].innerHTML = utils.secondToTime(this.video.duration);
+            }
+        });
+
+        // show video loaded bar: to inform interested parties of progress downloading the media
+        this.video.on('current', 'progress', (i, video) => {
+            const percentage = video.buffered.length ? video.buffered.end(video.buffered.length - 1) / video.duration : 0;
+            this.updateBar('loaded', percentage, 'width');
+        });
+
+        // video download error: an error occurs
+        this.video.on('all', 'error', () => {
+            this.notice(this.tran('This video fails to load'), -1);
+            this.trigger('pause');
+        });
+
+        // video can play: enough data is available that the media can be played
+        this.video.on('current', 'canplay', () => {
+            this.trigger('canplay');
+        });
+
+        // music end
+        this.ended = false;
+        this.video.on('all', 'ended', (i) => {
+            if (i === this.video.videos.length - 1) {
+                this.updateBar('played', 1, 'width');
+                if (!this.loop) {
+                    this.ended = true;
+                    this.pause();
+                    this.trigger('ended');
+                }
+                else {
+                    this.video.switch(0);
+                    this.video.play();
+                }
+            }
+        });
+
+        this.video.on('current', 'play', () => {
+            if (this.paused) {
+                this.play();
+            }
+        });
+
+        this.video.on('current', 'pause', () => {
+            if (!this.paused) {
+                this.pause();
+            }
+        });
+
+        // control volume
+        this.video.attr('volume', parseInt(this.element.getElementsByClassName('dplayer-volume-bar-inner')[0].style.width) / 100);
+    }
+
+    switchQuality (index) {
+        if (this.qualityIndex === index || this.switchingQuality) {
+            return;
+        }
+        else {
+            this.qualityIndex = index;
+        }
+        this.switchingQuality = true;
+        this.video.pause();
+        this.quality = this.option.video.quality[index];
+        const videoHTML = html.video(false, null, this.option.screenshot, 'auto', this.quality.url);
+        const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;
+        const parent = this.element.getElementsByClassName('dplayer-video-wrap')[0];
+        parent.prepend(videoEle);
+        this.prevVideo = this.video;
+        this.video = new Video([videoEle], this.prevVideo.duration);
+        this.initVideo();
+        this.video.seek(this.prevVideo.currentTime());
+        this.notice(`${this.tran('Switching to')} ${this.quality.name} ${this.tran('quality')}`, -1);
+        this.video.on('current', 'canplay', () => {
+            if (this.prevVideo) {
+                parent.removeChild(this.prevVideo.current);
+                this.prevVideo = null;
+                this.video.current.classList.add('dplayer-video-current');
+                this.video.play();
+                this.notice(`${this.tran('Switched to')} ${this.quality.name} ${this.tran('quality')}`);
+                this.switchingQuality = false;
+            }
+        });
+    }
+
     timeTipsHandler (pbar, timeTips) {
         // http://stackoverflow.com/questions/1480133/how-can-i-get-an-objects-absolute-position-on-the-page-in-javascript
         const cumulativeOffset = (element) => {
@@ -1185,6 +1236,21 @@ class DPlayer {
             }
         };
     }
+
+    notice (text, time) {
+        const noticeEle = this.element.getElementsByClassName('dplayer-notice')[0];
+        noticeEle.innerHTML = text;
+        noticeEle.style.opacity = 1;
+        if (this.noticeTime) {
+            clearTimeout(this.noticeTime);
+        }
+        if (time && time < 0) {
+            return;
+        }
+        this.noticeTime = setTimeout(() => {
+            noticeEle.style.opacity = 0;
+        }, time || 2000);
+    }
 }
 
 module.exports = DPlayer;

+ 91 - 2
src/DPlayer.scss

@@ -167,6 +167,10 @@
         .dplayer-video-current {
             display: block;
         }
+
+        .dplayer-video-prepare {
+            display: none;
+        }
     }
 
     .dplayer-danmaku {
@@ -381,7 +385,7 @@
                 font-size: 12px;
                 text-align: center;
                 opacity: 1;
-                transition: left opacity .1s ease-in-out;
+                transition: opacity .1s ease-in-out;
             }
 
             .dplayer-bar {
@@ -460,7 +464,7 @@
             }
 
             .dplayer-icon {
-                width: 46px;
+                width: 40px;
                 height: 100%;
                 border: none;
                 background-color: transparent;
@@ -476,6 +480,13 @@
                     opacity: 1;
                 }
 
+                &.dplayer-quality-icon {
+                    color: #fff;
+                    width: auto;
+                    line-height: 22px;
+                    font-size: 14px;
+                }
+
                 &.dplayer-comment-icon {
                     padding: 10px 9px 9px;
                 }
@@ -684,6 +695,55 @@
                 }
             }
 
+            .dplayer-quality {
+                position: relative;
+                display: inline-block;
+                height: 100%;
+                z-index: 2;
+
+                &:hover {
+                    .dplayer-quality-list {
+                        display: block;
+                    }
+                    .dplayer-quality-mask {
+                        display: block;
+                    }
+                }
+
+                .dplayer-quality-mask {
+                    display: none;
+                    position: absolute;
+                    bottom: 38px;
+                    left: -18px;
+                    width: 80px;
+                    padding-bottom: 12px;
+                }
+
+                .dplayer-quality-list {
+                    display: none;
+                    font-size: 12px;
+                    width: 80px;
+                    border-radius: 2px;
+                    background: rgba(28, 28, 28, 0.9);
+                    padding: 5px 0;
+                    transition: all .3s ease-in-out;
+                    overflow: hidden;
+                    color: #fff;
+                    text-align: center;
+                }
+
+                .dplayer-quality-item {
+                    height: 25px;
+                    box-sizing: border-box;
+                    cursor: pointer;
+                    line-height: 25px;
+
+                    &:hover {
+                        background-color: rgba(255,255,255,.1);
+                    }
+                }
+            }
+
             .dplayer-comment {
                 display: inline-block;
                 height: 100%;
@@ -974,6 +1034,35 @@
             }
         }
     }
+
+    .dplayer-logo {
+        pointer-events: none;
+        position: absolute;
+        left: 20px;
+        top: 20px;
+        max-width: 50px;
+        max-height: 50px;
+
+        img {
+            max-width: 100%;
+            max-height: 100%;
+        }
+    }
+
+    .dplayer-notice {
+        opacity: 0;
+        position: absolute;
+        bottom: 60px;
+        left: 20px;
+        font-size: 14px;
+        border-radius: 2px;
+        background: rgba(28, 28, 28, 0.9);
+        padding: 7px 20px;
+        transition: all .3s ease-in-out;
+        overflow: hidden;
+        color: #fff;
+        pointer-events: none;
+    }
 }
 
 @keyframes my-face {

+ 41 - 9
src/html.js

@@ -1,15 +1,18 @@
 const svg = require('./svg.js');
 
-module.exports = {
+const html = {
     main: (option, index, tran) => {
         let videos = ``;
         for (let i = 0; i < option.video.url.length; i++) {
-            videos += `<video class="dplayer-video ${i === 0 ? `dplayer-video-current"` : ``}" ${option.video.pic ? `poster="${option.video.pic}"` : ``} webkit-playsinline playsinline ${option.screenshot ? `crossorigin="anonymous"` : ``} preload="${option.video.url.length ? 'metadata' : option.preload}" src="${option.video.url[i]}"></video>`;
+            videos += html.video(i === 0, option.video.pic, option.screenshot, option.video.url.length ? 'metadata' : option.preload, option.video.url[i]);
         }
         return `
         <div class="dplayer-mask"></div>
         <div class="dplayer-video-wrap">
             ${videos}
+            ${option.logo ? `
+            <div class="dplayer-logo"><img src="${option.logo}"></div>
+            ` : ``}
             <div class="dplayer-danmaku">
                 <div class="dplayer-danmaku-item dplayer-danmaku-item--demo"></div>
             </div>
@@ -67,8 +70,16 @@ module.exports = {
                 <span class="dplayer-time"><span class="dplayer-ptime">0:00</span> / <span class="dplayer-dtime">0:00</span></span>
             </div>
             <div class="dplayer-icons dplayer-icons-right">
+                ${option.video.quality ? `
+                <div class="dplayer-quality">
+                    <button class="dplayer-icon dplayer-quality-icon">${option.video.quality[option.video.defaultQuality].name}</button>
+                    <div class="dplayer-quality-mask">
+                        ${html.qualityList(option.video.quality)}
+                    </div>
+                </div>
+                ` : ``}
                 ${option.screenshot ? `
-                <a href="#" class="dplayer-icon dplayer-camera-icon"}dplayer-volume>
+                <a href="#" class="dplayer-icon dplayer-camera-icon">
                     ${svg('camera')}
                 </a>
                 ` : ``}
@@ -155,13 +166,32 @@ module.exports = {
                 </div>
             </div>
         </div>
-        <div class="dplayer-menu">
-            <div class="dplayer-menu-item"><span class="dplayer-menu-label"><a target="_blank" href="http://diygod.me/">${tran('About author')}</a></span></div>
-            <div class="dplayer-menu-item"><span class="dplayer-menu-label"><a target="_blank" href="https://github.com/DIYgod/DPlayer/issues">${tran('DPlayer feedback')}</a></span></div>
-            <div class="dplayer-menu-item"><span class="dplayer-menu-label"><a target="_blank" href="https://github.com/DIYgod/DPlayer">${tran('About DPlayer')}</a></span></div>
-        </div>`;
+        ${html.contextmenuList(option.contextmenu)}
+        <div class="dplayer-notice"></div>`;
+    },
+
+    contextmenuList: (contextmenu) => {
+        let result = '<div class="dplayer-menu">';
+        for (let i = 0; i < contextmenu.length; i++) {
+            result += `<div class="dplayer-menu-item"><span class="dplayer-menu-label"><a target="_blank" href="${contextmenu[i].link}">${contextmenu[i].text}</a></span></div>`;
+        }
+        result += '</div>';
+
+        return result;
     },
 
+    qualityList: (quality) => {
+        let result = '<div class="dplayer-quality-list">';
+        for (let i = 0; i < quality.length; i++) {
+            result += `<div class="dplayer-quality-item" data-index="${i}">${quality[i].name}</div>`;
+        }
+        result += '</div>';
+
+        return result;
+    },
+
+    video: (current, pic, screenshot, preload, url) => `<video class="dplayer-video ${current ? `dplayer-video-current"` : ``}" ${pic ? `poster="${pic}"` : ``} webkit-playsinline playsinline ${screenshot ? `crossorigin="anonymous"` : ``} ${preload ? `preload="${preload}"` : ``} src="${url}"></video>`,
+
     setting: (tran) => ({
         'original': `
             <div class="dplayer-setting-item dplayer-setting-speed">
@@ -214,4 +244,6 @@ module.exports = {
                 <span class="dplayer-label">2</span>
             </div>`
     }) 
-};
+};
+
+module.exports = html;

+ 6 - 2
src/i18n.js

@@ -11,10 +11,14 @@ const tranZH = {
     'Speed': '速度',
     'Opacity for danmaku': '弹幕透明度',
     'Normal': '正常',
-    'Please input danmaku!': '要输入弹幕内容啊喂!',
+    'Please input danmaku content!': '要输入弹幕内容啊喂!',
     'Set danmaku color': '设置弹幕颜色',
     'Set danmaku type': '设置弹幕类型',
-    'Danmaku': '弹幕'
+    'Danmaku': '弹幕',
+    'This video fails to load': '视频加载失败',
+    'Switching to': '正在切换至',
+    'Switched to': '已经切换至',
+    'quality': '画质',
 };
 
 module.exports = function (lang) {

+ 19 - 1
src/option.js

@@ -17,7 +17,21 @@ module.exports = (option) => {
         screenshot: false,
         hotkey: true,
         preload: 'auto',
-        apiBackend: defaultApiBackend
+        apiBackend: defaultApiBackend,
+        contextmenu: [
+            {
+                text: '关于作者',
+                link: 'http://diygod.me'
+            },
+            {
+                text: '播放器意见反馈',
+                link: 'https://github.com/DIYgod/DPlayer/issues'
+            },
+            {
+                text: '关于 DPlayer 播放器',
+                link: 'https://github.com/DIYgod/DPlayer'
+            }
+        ]
     };
     for (const defaultKey in defaultOption) {
         if (defaultOption.hasOwnProperty(defaultKey) && !option.hasOwnProperty(defaultKey)) {
@@ -34,5 +48,9 @@ module.exports = (option) => {
         option.danmaku.user = 'DIYgod';
     }
 
+    if (option.video.quality) {
+        option.video.url = [option.video.quality[option.video.defaultQuality].url];
+    }
+
     return option;
 };

+ 6 - 4
src/video.js

@@ -1,19 +1,21 @@
 class Video {
-    constructor (videos) {
+    constructor (videos, duration) {
         this.videos = videos;
         this.multi = this.videos.length > 1;
         this.index = 0;
         this.current = this.videos[this.index];
 
-        this.duration = 0;
+        this.duration = duration || 0;
         this.durationArr = [];
         this.eventAll = [];
         this.eventCurrent = [];
-
+        
         this.on('all', 'durationchange', (i, video) => {
             if (video.duration !== 1) {           // some Android browsers will output 1 at first
                 this.durationArr[i] = video.duration;
-                this.duration = this.durationArr.reduce((sum, cur) => sum + cur);
+                if (!duration) {
+                    this.duration = this.durationArr.reduce((sum, cur) => sum + cur);                    
+                }
             }
         });
         this.on('current', 'end', () => {

部分文件因为文件数量过多而无法显示