Browse Source

updated from upstream | 01/07/2019 at 17:07

Jesús 5 months ago
parent
commit
69ccd79dbb
Signed by: Jesús <heckyel@hyperbola.info> GPG Key ID: F6EE7BC59A315766
92 changed files with 33167 additions and 13588 deletions
  1. 30
    0
      .eslintrc
  2. 1
    2
      .npmignore
  3. 4
    2
      build.json
  4. 32
    0
      changelog.md
  5. 1
    1
      demo/dist/demo.css
  6. 16679
    4416
      demo/dist/demo.js
  7. 1
    1
      demo/dist/demo.min.js
  8. 1
    1
      demo/dist/demo.min.js.map
  9. 1
    1
      demo/dist/error.css
  10. 1
    1
      demo/error.html
  11. 267
    31
      demo/index.html
  12. 14
    0
      demo/package.json
  13. 56
    184
      demo/src/js/demo.js
  14. 78
    0
      demo/src/js/sources.js
  15. 31
    0
      demo/src/js/tab-focus.js
  16. 5
    0
      demo/src/js/toggle-class.js
  17. 10
    9
      demo/src/sass/components/buttons.scss
  18. 11
    0
      demo/src/sass/components/header.scss
  19. 1
    1
      demo/src/sass/components/icons.scss
  20. 0
    1
      demo/src/sass/components/links.scss
  21. 2
    16
      demo/src/sass/components/players.scss
  22. 1
    2
      demo/src/sass/layout/core.scss
  23. 14
    0
      demo/src/sass/lib/animation.scss
  24. 5
    5
      demo/src/sass/lib/fontface.scss
  25. 1
    1
      demo/src/sass/lib/mixins.scss
  26. 26
    16
      demo/src/sass/settings/colors.scss
  27. 1
    1
      demo/src/sass/settings/cosmetic.scss
  28. 3
    0
      demo/src/sass/settings/plyr.scss
  29. 1
    1
      demo/src/sass/settings/spacing.scss
  30. 1
    2
      demo/src/sass/type/base.scss
  31. 2
    1
      demo/src/sass/type/headings.scss
  32. 28
    0
      demo/yarn.lock
  33. BIN
      dist/blank.mp4
  34. 1
    1
      dist/plyr.css
  35. 793
    588
      dist/plyr.js
  36. 1
    1
      dist/plyr.min.js
  37. 1
    1
      dist/plyr.min.js.map
  38. 1
    1
      dist/plyr.min.mjs
  39. 1
    1
      dist/plyr.min.mjs.map
  40. 793
    588
      dist/plyr.mjs
  41. 5633
    2934
      dist/plyr.polyfilled.js
  42. 1
    1
      dist/plyr.polyfilled.min.js
  43. 1
    1
      dist/plyr.polyfilled.min.js.map
  44. 1
    1
      dist/plyr.polyfilled.min.mjs
  45. 1
    1
      dist/plyr.polyfilled.min.mjs.map
  46. 5633
    2934
      dist/plyr.polyfilled.mjs
  47. 31
    19
      gulpfile.js
  48. 1
    1
      license.md
  49. 34
    33
      package.json
  50. 0
    3
      plyr.code-workspace
  51. 32
    17
      readme.md
  52. 19
    15
      src/js/captions.js
  53. 7
    13
      src/js/config/defaults.js
  54. 305
    271
      src/js/controls.js
  55. 0
    4
      src/js/fullscreen.js
  56. 8
    3
      src/js/html5.js
  57. 20
    30
      src/js/listeners.js
  58. 53
    40
      src/js/plugins/ads.js
  59. 652
    0
      src/js/plugins/preview-thumbnails.js
  60. 6
    8
      src/js/plugins/vimeo.js
  61. 52
    54
      src/js/plugins/youtube.js
  62. 105
    31
      src/js/plyr.js
  63. 2
    1
      src/js/plyr.polyfilled.js
  64. 28
    16
      src/js/ui.js
  65. 10
    6
      src/js/utils/animation.js
  66. 12
    17
      src/js/utils/elements.js
  67. 0
    1
      src/js/utils/events.js
  68. 2
    2
      src/js/utils/i18n.js
  69. 19
    0
      src/js/utils/load-image.js
  70. 14
    0
      src/js/utils/load-script.js
  71. 75
    0
      src/js/utils/load-sprite.js
  72. 17
    0
      src/js/utils/numbers.js
  73. 50
    13
      src/js/utils/style.js
  74. 0
    1
      src/js/utils/time.js
  75. 0
    5
      src/sass/components/control.scss
  76. 33
    31
      src/sass/components/controls.scss
  77. 8
    7
      src/sass/components/progress.scss
  78. 33
    0
      src/sass/components/video.scss
  79. 2
    1
      src/sass/lib/mixins.scss
  80. 1
    1
      src/sass/plugins/ads.scss
  81. 118
    0
      src/sass/plugins/preview-thumbnails.scss
  82. 1
    2
      src/sass/plyr.scss
  83. 1
    1
      src/sass/settings/badges.scss
  84. 13
    5
      src/sass/settings/colors.scss
  85. 1
    1
      src/sass/settings/controls.scss
  86. 2
    2
      src/sass/settings/menus.scss
  87. 2
    2
      src/sass/settings/progress.scss
  88. 1
    1
      src/sass/settings/sliders.scss
  89. 1
    1
      src/sass/settings/tooltips.scss
  90. 0
    0
      src/sprite/plyr-restart.svg
  91. 0
    0
      src/sprite/plyr-volume.svg
  92. 1226
    1180
      yarn.lock

+ 30
- 0
.eslintrc View File

@@ -0,0 +1,30 @@
1
+{
2
+    "parser": "babel-eslint",
3
+    "extends": ["airbnb-base", "prettier"],
4
+    "plugins": ["simple-import-sort", "import"],
5
+    "env": {
6
+        "browser": true,
7
+        "es6": true
8
+    },
9
+    "globals": {
10
+        "Plyr": false,
11
+        "jQuery": false
12
+    },
13
+    "rules": {
14
+        "import/no-cycle": "warn",
15
+        "padding-line-between-statements": [
16
+            "error",
17
+            {
18
+                "blankLine": "never",
19
+                "prev": ["singleline-const", "singleline-let", "singleline-var"],
20
+                "next": ["singleline-const", "singleline-let", "singleline-var"]
21
+            }
22
+        ],
23
+        "sort-imports": "off",
24
+        "import/order": "off",
25
+        "simple-import-sort/sort": "error"
26
+    },
27
+    "parserOptions": {
28
+        "sourceType": "module"
29
+    }
30
+}

+ 1
- 2
.npmignore View File

@@ -9,5 +9,4 @@ yarn.lock
9 9
 package-lock.json
10 10
 *.mp4
11 11
 *.webm
12
-!dist/blank.mp4
13
-venv/
12
+!dist/blank.mp4

+ 4
- 2
build.json View File

@@ -10,13 +10,15 @@
10 10
             "src": "./src/js/plyr.polyfilled.js",
11 11
             "dist": "./dist/",
12 12
             "formats": ["es", "umd"],
13
-            "namespace": "Plyr"
13
+            "namespace": "Plyr",
14
+            "polyfill": true
14 15
         },
15 16
         "demo.js": {
16 17
             "src": "./demo/src/js/demo.js",
17 18
             "dist": "./demo/dist/",
18 19
             "formats": ["iife"],
19
-            "namespace": "Demo"
20
+            "namespace": "Demo",
21
+            "polyfill": true
20 22
         }
21 23
     },
22 24
     "css": {

+ 32
- 0
changelog.md View File

@@ -1,3 +1,35 @@
1
+## v3.5.6
2
+
3
+-   Another Edge fix (thanks Nick Hawk via Slack)
4
+
5
+## v3.5.5
6
+
7
+-   YouTube fix for when there are other embeds on the page (thanks @aFarkas)
8
+-   Separated demo dependencies into their own package.json
9
+-   Fix for Edge controls flexbox issue when resizing the player (thanks Nick Hawk via Slack)
10
+-   More aspect ratio fixes
11
+
12
+## v3.5.4
13
+
14
+-   Added: Set download URL via new setter
15
+-   Improvement: The order of the `controls` option now effects the order in the DOM - i.e. you can re-order the controls - Note: this may break any custom CSS you have setup. Please see the changes in the PR to the default SASS
16
+-   Fixed issue with empty controls and preview thumbs
17
+-   Fixed issue with setGutter call (from Sentry)
18
+-   Fixed issue with initial selected speed not working
19
+-   Added notes on `autoplay` config option and browser compatibility
20
+-   Fixed issue with ads volume not matching current content volume
21
+-   Fixed race condition where ads were loading during source change
22
+-   Improvement: Automatic aspect ratio for YouTube is now supported, meaning all aspect ratios are set based on media content - Note: we're now using a different API to get YouTube video metadata so you may need to adjust any CSPs you have setup
23
+-   Fix for menu in the Shadow DOM (thanks @emielbeinema)
24
+
25
+## v3.5.3
26
+
27
+-   Improved the usage of the `ratio` config option; it now works as expected and for all video types. The default has not changed, it is to dynamically, where possible (except YouTube where 16:9 is used) determine the ratio from the media source so this is not a breaking change.
28
+-   Added new `ratio` getter and setter
29
+-   Fix: Properly clear all timeouts on destroy
30
+-   Fix: Allow absolute paths in preview thumbnails
31
+-   Improvement: Allow optional hours and ms in VTT parser in preview thumbnails
32
+
1 33
 ## v3.5.2
2 34
 
3 35
 -   Fixed issue where the preview thumbnail was present while scrubbing

+ 1
- 1
demo/dist/demo.css
File diff suppressed because it is too large
View File


+ 16679
- 4416
demo/dist/demo.js
File diff suppressed because it is too large
View File


+ 1
- 1
demo/dist/demo.min.js
File diff suppressed because it is too large
View File


+ 1
- 1
demo/dist/demo.min.js.map
File diff suppressed because it is too large
View File


+ 1
- 1
demo/dist/error.css
File diff suppressed because it is too large
View File


+ 1
- 1
demo/error.html View File

@@ -28,4 +28,4 @@
28 28
     </main>
29 29
 </body>
30 30
 
31
-</html>
31
+</html>

+ 267
- 31
demo/index.html View File

@@ -1,41 +1,277 @@
1 1
 <!DOCTYPE html>
2 2
 <html lang="en">
3 3
     <head>
4
-        <meta charset="UTF-8"/>
5
-        <title>Document</title>
6
-        <style>
7
-         video {
8
-             width: 100%;
9
-             height: auto;
10
-         }
11
-        </style>
12
-        <link href="../dist/plyr.css" rel="stylesheet"/>
13
-    </head>
14
-    <body>
4
+        <meta charset="utf-8" />
5
+        <title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
6
+        <meta
7
+            name="description"
8
+            property="og:description"
9
+            content="A simple HTML5 media player with custom controls and WebVTT captions."
10
+        />
11
+        <meta name="author" content="Sam Potts" />
12
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
13
+
14
+        <!-- Icons -->
15
+        <link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico" />
16
+        <link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32" />
17
+        <link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
18
+        <link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
19
+
20
+        <!-- Open Graph -->
21
+        <meta
22
+            property="og:title"
23
+            content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
24
+        />
25
+        <meta property="og:site_name" content="Plyr" />
26
+        <meta property="og:url" content="https://plyr.io" />
27
+        <meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png" />
15 28
 
16
-        <video class="player-plyr" controls playsinline poster="./media/View_From_A_Blue_Moon_Trailer-HD.jpg">
17
-            <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" type="video/mp4" data-res="576">
18
-            <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" data-res="720">
19
-            <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" data-res="1080">
20
-            <!-- Caption files -->
21
-            <track kind="captions" label="English" srclang="en" src="./media/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
22
-            <track kind="captions" label="Français" srclang="fr" src="./media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
23
-        </video>
29
+        <!-- Twitter -->
30
+        <meta name="twitter:card" content="summary" />
31
+        <meta name="twitter:site" content="@sam_potts" />
32
+        <meta name="twitter:creator" content="@sam_potts" />
33
+        <meta name="twitter:card" content="summary_large_image" />
24 34
 
25
-        <!--Plyr-->
35
+        <!-- Docs styles -->
36
+        <link rel="stylesheet" href="dist/demo.css" />
37
+
38
+        <!-- Preload -->
39
+        <link
40
+            rel="preload"
41
+            as="font"
42
+            crossorigin
43
+            type="font/woff2"
44
+            href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2"
45
+        />
46
+        <link
47
+            rel="preload"
48
+            as="font"
49
+            crossorigin
50
+            type="font/woff2"
51
+            href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
52
+        />
53
+
54
+        <!-- Google Analytics-->
55
+        <script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
26 56
         <script>
27
-         document.addEventListener('DOMContentLoaded', () => {
28
-             const players = Array.from(document.querySelectorAll('.player-plyr')).map(player => new Plyr(player));
29
-         });
30
-
31
-         // set time
32
-         /* const setTime = document.getElementById("player"); // only ID */
33
-         const setTime = document.querySelector(".player-plyr");
34
-         setTime.currentTime = 20;
35
-         console.log(setTime.currentTime);
57
+            window.dataLayer = window.dataLayer || [];
58
+            function gtag() {
59
+                dataLayer.push(arguments);
60
+            }
61
+            gtag('js', new Date());
62
+            gtag('config', 'UA-132699580-1');
36 63
         </script>
37
-        <!--EndPlyr-->
64
+    </head>
65
+
66
+    <body>
67
+        <div class="grid">
68
+            <header>
69
+                <h1>Pl<span>a</span>y<span>e</span>r</h1>
70
+                <p>
71
+                    A simple, accessible and customisable media player for
72
+                    <button type="button" class="faux-link" data-source="video">
73
+                        <svg class="icon">
74
+                            <title>HTML5</title>
75
+                            <path
76
+                                d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
77
+                            ></path></svg
78
+                        >Video</button
79
+                    >,
80
+                    <button type="button" class="faux-link" data-source="audio">
81
+                        <svg class="icon">
82
+                            <title>HTML5</title>
83
+                            <path
84
+                                d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
85
+                            ></path></svg
86
+                        >Audio</button
87
+                    >,
88
+                    <button type="button" class="faux-link" data-source="youtube">
89
+                        <svg class="icon" role="presentation">
90
+                            <title>YouTube</title>
91
+                            <path
92
+                                d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
93
+                   s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
94
+                    M6,11V5l5,3L6,11z"
95
+                            ></path></svg
96
+                        >YouTube
97
+                    </button>
98
+                    and
99
+                    <button type="button" class="faux-link" data-source="vimeo">
100
+                        <svg class="icon" role="presentation">
101
+                            <title>Vimeo</title>
102
+                            <path
103
+                                d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
104
+                       C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
105
+                       c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
106
+                            ></path></svg
107
+                        >Vimeo
108
+                    </button>
109
+                </p>
110
+
111
+                <p>
112
+                    Premium video monitization from
113
+                    <a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
114
+                        <img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
115
+                        <span class="sr-only">ai.vi</span>
116
+                    </a>
117
+                </p>
118
+
119
+                <div class="call-to-action">
120
+                    <a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
121
+                        <svg class="icon" role="presentation">
122
+                            <title>GitHub</title>
123
+                            <path
124
+                                d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
125
+            C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
126
+            c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
127
+            c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
128
+            c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
129
+            c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"
130
+                            ></path>
131
+                        </svg>
132
+                        Download on GitHub
133
+                    </a>
134
+                </div>
135
+            </header>
136
+
137
+            <main>
138
+                <div id="container">
139
+                    <video
140
+                        controls
141
+                        crossorigin
142
+                        playsinline
143
+                        poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
144
+                        id="player"
145
+                    >
146
+                        <!-- Video files -->
147
+                        <source
148
+                            src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
149
+                            type="video/mp4"
150
+                            data-res="576"
151
+                        />
152
+                        <source
153
+                            src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4"
154
+                            type="video/mp4"
155
+                            data-res="720"
156
+                        />
157
+                        <source
158
+                            src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4"
159
+                            type="video/mp4"
160
+                            data-res="1080"
161
+                        />
162
+
163
+                        <!-- Caption files -->
164
+                        <track
165
+                            kind="captions"
166
+                            label="English"
167
+                            srclang="en"
168
+                            src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
169
+                            default
170
+                        />
171
+                        <track
172
+                            kind="captions"
173
+                            label="Français"
174
+                            srclang="fr"
175
+                            src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"
176
+                        />
177
+
178
+                        <!-- Fallback for browsers that don't support the <video> element -->
179
+                        <a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download
180
+                            >Download</a
181
+                        >
182
+                    </video>
183
+                </div>
184
+
185
+                <ul>
186
+                    <li class="plyr__cite plyr__cite--video" hidden>
187
+                        <small>
188
+                            <svg class="icon">
189
+                                <title>HTML5</title>
190
+                                <path
191
+                                    d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
192
+                                ></path>
193
+                            </svg>
194
+                            <a
195
+                                href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323"
196
+                                target="_blank"
197
+                                >View From A Blue Moon</a
198
+                            >
199
+                            &copy; Brainfarm
200
+                        </small>
201
+                    </li>
202
+                    <li class="plyr__cite plyr__cite--audio" hidden>
203
+                        <small>
204
+                            <svg class="icon" title="HTML5">
205
+                                <title>HTML5</title>
206
+                                <path
207
+                                    d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
208
+                                ></path>
209
+                            </svg>
210
+                            <a href="http://www.kishibashi.com/" target="_blank"
211
+                                >Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;</a
212
+                            >
213
+                            &copy; Kishi Bashi
214
+                        </small>
215
+                    </li>
216
+                    <li class="plyr__cite plyr__cite--youtube" hidden>
217
+                        <small>
218
+                            <a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank"
219
+                                >View From A Blue Moon</a
220
+                            >
221
+                            on&nbsp;
222
+                            <span class="color--youtube">
223
+                                <svg class="icon" role="presentation">
224
+                                    <title>YouTube</title>
225
+                                    <path
226
+                                        d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
227
+                                   s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
228
+                                    M6,11V5l5,3L6,11z"
229
+                                    ></path></svg
230
+                                >YouTube
231
+                            </span>
232
+                        </small>
233
+                    </li>
234
+                    <li class="plyr__cite plyr__cite--vimeo" hidden>
235
+                        <small>
236
+                            <a href="https://vimeo.com/76979871" target="_blank">The New Vimeo Player</a> on&nbsp;
237
+                            <span class="color--vimeo">
238
+                                <svg class="icon" role="presentation">
239
+                                    <title>Vimeo</title>
240
+                                    <path
241
+                                        d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
242
+                               C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
243
+                               c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
244
+                                    ></path></svg
245
+                                >Vimeo
246
+                            </span>
247
+                        </small>
248
+                    </li>
249
+                </ul>
250
+            </main>
251
+        </div>
252
+
253
+        <aside>
254
+            <svg class="icon">
255
+                <title>Twitter</title>
256
+                <path
257
+                    d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
258
+       C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
259
+       c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
260
+       c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"
261
+                ></path>
262
+            </svg>
263
+            <p>
264
+                If you think Plyr's good,
265
+                <a
266
+                    href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&amp;url=http%3A%2F%2Fplyr.io&amp;via=Sam_Potts"
267
+                    target="_blank"
268
+                    class="js-shr"
269
+                    >tweet it</a
270
+                >
271
+                👍
272
+            </p>
273
+        </aside>
38 274
 
39
-        <script src="../dist/plyr.min.js"></script>
275
+        <script src="dist/demo.js" crossorigin="anonymous"></script>
40 276
     </body>
41 277
 </html>

+ 14
- 0
demo/package.json View File

@@ -0,0 +1,14 @@
1
+{
2
+    "name": "plyr-demo",
3
+    "version": "1.0.0",
4
+    "description": "Demo for Plyr",
5
+    "homepage": "https://plyr.io",
6
+    "author": "Sam Potts <sam@potts.es>",
7
+    "dependencies": {
8
+        "core-js": "^3.1.4",
9
+        "custom-event-polyfill": "^1.0.7",
10
+        "raven-js": "^3.27.2",
11
+        "shr-buttons": "2.0.3",
12
+        "url-polyfill": "^1.1.5"
13
+    }
14
+}

+ 56
- 184
demo/src/js/demo.js View File

@@ -4,8 +4,16 @@
4 4
 // Please see readme.md in the root or github.com/sampotts/plyr
5 5
 // ==========================================================================
6 6
 
7
+import './tab-focus';
8
+import 'custom-event-polyfill';
9
+import 'url-polyfill';
10
+
7 11
 import Raven from 'raven-js';
12
+import Shr from 'shr-buttons';
13
+
8 14
 import Plyr from '../../../src/js/plyr';
15
+import sources from './sources';
16
+import toggleClass from './toggle-class';
9 17
 
10 18
 (() => {
11 19
     const { host } = window.location;
@@ -17,45 +25,15 @@ import Plyr from '../../../src/js/plyr';
17 25
     document.addEventListener('DOMContentLoaded', () => {
18 26
         Raven.context(() => {
19 27
             const selector = '#player';
20
-            const container = document.getElementById('container');
21
-
22
-            if (window.Shr) {
23
-                window.Shr.setup('.js-shr-button', {
24
-                    count: {
25
-                        classname: 'button__count',
26
-                    },
27
-                });
28
-            }
29
-
30
-            // Setup tab focus
31
-            const tabClassName = 'tab-focus';
32
-
33
-            // Remove class on blur
34
-            document.addEventListener('focusout', event => {
35
-                if (!event.target.classList || container.contains(event.target)) {
36
-                    return;
37
-                }
38
-
39
-                event.target.classList.remove(tabClassName);
40
-            });
41
-
42
-            // Add classname to tabbed elements
43
-            document.addEventListener('keydown', event => {
44
-                if (event.keyCode !== 9) {
45
-                    return;
46
-                }
47 28
 
48
-                // Delay the adding of classname until the focus has changed
49
-                // This event fires before the focusin event
50
-                setTimeout(() => {
51
-                    const focused = document.activeElement;
52
-
53
-                    if (!focused || !focused.classList || container.contains(focused)) {
54
-                        return;
55
-                    }
56
-
57
-                    focused.classList.add(tabClassName);
58
-                }, 10);
29
+            // Setup share buttons
30
+            Shr.setup('.js-shr', {
31
+                count: {
32
+                    className: 'button__count',
33
+                },
34
+                wrapper: {
35
+                    className: 'button--with-count',
36
+                },
59 37
             });
60 38
 
61 39
             // Setup the player
@@ -93,142 +71,45 @@ import Plyr from '../../../src/js/plyr';
93 71
 
94 72
             // Setup type toggle
95 73
             const buttons = document.querySelectorAll('[data-source]');
96
-            const types = {
97
-                video: 'video',
98
-                audio: 'audio',
99
-                youtube: 'youtube',
100
-                vimeo: 'vimeo',
101
-            };
102
-            let currentType = window.location.hash.replace('#', '');
103
-            const historySupport = window.history && window.history.pushState;
74
+            const types = Object.keys(sources);
75
+            const historySupport = Boolean(window.history && window.history.pushState);
76
+            let currentType = window.location.hash.substring(1);
77
+            const hasCurrentType = !currentType.length;
104 78
 
105
-            // Toggle class on an element
106
-            function toggleClass(element, className, state) {
107
-                if (element) {
108
-                    element.classList[state ? 'add' : 'remove'](className);
109
-                }
79
+            function render(type) {
80
+                // Remove active classes
81
+                Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
82
+
83
+                // Set active on parent
84
+                toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
85
+
86
+                // Show cite
87
+                Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
88
+                    // eslint-disable-next-line no-param-reassign
89
+                    cite.hidden = true;
90
+                });
91
+
92
+                document.querySelector(`.plyr__cite--${type}`).hidden = false;
110 93
             }
111 94
 
112 95
             // Set a new source
113
-            function newSource(type, init) {
96
+            function setSource(type, init) {
114 97
                 // Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
115 98
                 if (
116
-                    !(type in types) ||
99
+                    !types.includes(type) ||
117 100
                     (!init && type === currentType) ||
118
-                    (!currentType.length && type === types.video)
101
+                    (!currentType.length && type === 'video')
119 102
                 ) {
120 103
                     return;
121 104
                 }
122 105
 
123
-                switch (type) {
124
-                    case types.video:
125
-                        player.source = {
126
-                            type: 'video',
127
-                            title: 'View From A Blue Moon',
128
-                            sources: [
129
-                                {
130
-                                    src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
131
-                                    type: 'video/mp4',
132
-                                    size: 576,
133
-                                },
134
-                                {
135
-                                    src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
136
-                                    type: 'video/mp4',
137
-                                    size: 720,
138
-                                },
139
-                                {
140
-                                    src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
141
-                                    type: 'video/mp4',
142
-                                    size: 1080,
143
-                                },
144
-                                {
145
-                                    src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
146
-                                    type: 'video/mp4',
147
-                                    size: 1440,
148
-                                },
149
-                            ],
150
-                            poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
151
-                            tracks: [
152
-                                {
153
-                                    kind: 'captions',
154
-                                    label: 'English',
155
-                                    srclang: 'en',
156
-                                    src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
157
-                                    default: true,
158
-                                },
159
-                                {
160
-                                    kind: 'captions',
161
-                                    label: 'French',
162
-                                    srclang: 'fr',
163
-                                    src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
164
-                                },
165
-                            ],
166
-                        };
167
-
168
-                        break;
169
-
170
-                    case types.audio:
171
-                        player.source = {
172
-                            type: 'audio',
173
-                            title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
174
-                            sources: [
175
-                                {
176
-                                    src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
177
-                                    type: 'audio/mp3',
178
-                                },
179
-                                {
180
-                                    src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
181
-                                    type: 'audio/ogg',
182
-                                },
183
-                            ],
184
-                        };
185
-
186
-                        break;
187
-
188
-                    case types.youtube:
189
-                        player.source = {
190
-                            type: 'video',
191
-                            sources: [
192
-                                {
193
-                                    src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
194
-                                    provider: 'youtube',
195
-                                },
196
-                            ],
197
-                        };
198
-
199
-                        break;
200
-
201
-                    case types.vimeo:
202
-                        player.source = {
203
-                            type: 'video',
204
-                            sources: [
205
-                                {
206
-                                    src: 'https://vimeo.com/76979871',
207
-                                    provider: 'vimeo',
208
-                                },
209
-                            ],
210
-                        };
211
-
212
-                        break;
213
-
214
-                    default:
215
-                        break;
216
-                }
106
+                // Set the new source
107
+                player.source = sources[type];
217 108
 
218 109
                 // Set the current type for next time
219 110
                 currentType = type;
220 111
 
221
-                // Remove active classes
222
-                Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
223
-
224
-                // Set active on parent
225
-                toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
226
-
227
-                // Show cite
228
-                Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
229
-                    cite.setAttribute('hidden', '');
230
-                });
231
-                document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
112
+                render(type);
232 113
             }
233 114
 
234 115
             // Bind to each button
@@ -236,7 +117,7 @@ import Plyr from '../../../src/js/plyr';
236 117
                 button.addEventListener('click', () => {
237 118
                     const type = button.getAttribute('data-source');
238 119
 
239
-                    newSource(type);
120
+                    setSource(type);
240 121
 
241 122
                     if (historySupport) {
242 123
                         window.history.pushState({ type }, '', `#${type}`);
@@ -246,36 +127,27 @@ import Plyr from '../../../src/js/plyr';
246 127
 
247 128
             // List for backwards/forwards
248 129
             window.addEventListener('popstate', event => {
249
-                if (event.state && 'type' in event.state) {
250
-                    newSource(event.state.type);
130
+                if (event.state && Object.keys(event.state).includes('type')) {
131
+                    setSource(event.state.type);
251 132
                 }
252 133
             });
253 134
 
254
-            // On load
255
-            if (historySupport) {
256
-                const video = !currentType.length;
257
-
258
-                // If there's no current type set, assume video
259
-                if (video) {
260
-                    currentType = types.video;
261
-                }
135
+            // If there's no current type set, assume video
136
+            if (hasCurrentType) {
137
+                currentType = 'video';
138
+            }
262 139
 
263
-                // Replace current history state
264
-                if (currentType in types) {
265
-                    window.history.replaceState(
266
-                        {
267
-                            type: currentType,
268
-                        },
269
-                        '',
270
-                        video ? '' : `#${currentType}`,
271
-                    );
272
-                }
140
+            // Replace current history state
141
+            if (historySupport && types.includes(currentType)) {
142
+                window.history.replaceState({ type: currentType }, '', hasCurrentType ? '' : `#${currentType}`);
143
+            }
273 144
 
274
-                // If it's not video, load the source
275
-                if (currentType !== types.video) {
276
-                    newSource(currentType, true);
277
-                }
145
+            // If it's not video, load the source
146
+            if (currentType !== 'video') {
147
+                setSource(currentType, true);
278 148
             }
149
+
150
+            render(currentType);
279 151
         });
280 152
     });
281 153
 

+ 78
- 0
demo/src/js/sources.js View File

@@ -0,0 +1,78 @@
1
+const sources = {
2
+    video: {
3
+        type: 'video',
4
+        title: 'View From A Blue Moon',
5
+        sources: [
6
+            {
7
+                src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
8
+                type: 'video/mp4',
9
+                size: 576,
10
+            },
11
+            {
12
+                src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
13
+                type: 'video/mp4',
14
+                size: 720,
15
+            },
16
+            {
17
+                src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
18
+                type: 'video/mp4',
19
+                size: 1080,
20
+            },
21
+            {
22
+                src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
23
+                type: 'video/mp4',
24
+                size: 1440,
25
+            },
26
+        ],
27
+        poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
28
+        tracks: [
29
+            {
30
+                kind: 'captions',
31
+                label: 'English',
32
+                srclang: 'en',
33
+                src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
34
+                default: true,
35
+            },
36
+            {
37
+                kind: 'captions',
38
+                label: 'French',
39
+                srclang: 'fr',
40
+                src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
41
+            },
42
+        ],
43
+    },
44
+    audio: {
45
+        type: 'audio',
46
+        title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
47
+        sources: [
48
+            {
49
+                src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
50
+                type: 'audio/mp3',
51
+            },
52
+            {
53
+                src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
54
+                type: 'audio/ogg',
55
+            },
56
+        ],
57
+    },
58
+    youtube: {
59
+        type: 'video',
60
+        sources: [
61
+            {
62
+                src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
63
+                provider: 'youtube',
64
+            },
65
+        ],
66
+    },
67
+    vimeo: {
68
+        type: 'video',
69
+        sources: [
70
+            {
71
+                src: 'https://vimeo.com/76979871',
72
+                provider: 'vimeo',
73
+            },
74
+        ],
75
+    },
76
+};
77
+
78
+export default sources;

+ 31
- 0
demo/src/js/tab-focus.js View File

@@ -0,0 +1,31 @@
1
+// Setup tab focus
2
+const container = document.getElementById('container');
3
+const tabClassName = 'tab-focus';
4
+
5
+// Remove class on blur
6
+document.addEventListener('focusout', event => {
7
+    if (!event.target.classList || container.contains(event.target)) {
8
+        return;
9
+    }
10
+
11
+    event.target.classList.remove(tabClassName);
12
+});
13
+
14
+// Add classname to tabbed elements
15
+document.addEventListener('keydown', event => {
16
+    if (event.keyCode !== 9) {
17
+        return;
18
+    }
19
+
20
+    // Delay the adding of classname until the focus has changed
21
+    // This event fires before the focusin event
22
+    setTimeout(() => {
23
+        const focused = document.activeElement;
24
+
25
+        if (!focused || !focused.classList || container.contains(focused)) {
26
+            return;
27
+        }
28
+
29
+        focused.classList.add(tabClassName);
30
+    }, 10);
31
+});

+ 5
- 0
demo/src/js/toggle-class.js View File

@@ -0,0 +1,5 @@
1
+// Toggle class on an element
2
+const toggleClass = (element, className = '', toggle = false) =>
3
+    element && element.classList[toggle ? 'add' : 'remove'](className);
4
+
5
+export default toggleClass;

+ 10
- 9
demo/src/sass/components/buttons.scss View File

@@ -6,11 +6,9 @@
6 6
 .button,
7 7
 .button__count {
8 8
     align-items: center;
9
-    background: $color-button-background;
10 9
     border: 0;
11 10
     border-radius: $border-radius-base;
12 11
     box-shadow: 0 1px 1px rgba(#000, 0.1);
13
-    color: $color-button-text;
14 12
     display: inline-flex;
15 13
     padding: ($spacing-base * 0.75);
16 14
     position: relative;
@@ -21,14 +19,16 @@
21 19
 
22 20
 // Buttons
23 21
 .button {
22
+    background: $color-button-background;
23
+    color: $color-button-text;
24 24
     font-weight: $font-weight-bold;
25
-    padding-left: $spacing-base;
26
-    padding-right: $spacing-base;
25
+    padding-left: ($spacing-base * 1.25);
26
+    padding-right: ($spacing-base * 1.25);
27 27
     transition: all 0.2s ease;
28 28
 
29 29
     &:hover,
30 30
     &:focus {
31
-        color: $gray-dark;
31
+        background: $color-button-background-hover;
32 32
 
33 33
         // Remove the underline/border
34 34
         &::after {
@@ -38,7 +38,6 @@
38 38
 
39 39
     &:hover {
40 40
         box-shadow: 0 2px 2px rgba(#000, 0.1);
41
-        transform: translateY(-1px);
42 41
     }
43 42
 
44 43
     &:focus {
@@ -50,7 +49,7 @@
50 49
     }
51 50
 
52 51
     &:active {
53
-        transform: translateY(1px);
52
+        top: 1px;
54 53
     }
55 54
 }
56 55
 
@@ -66,12 +65,14 @@
66 65
 // Count bubble
67 66
 .button__count {
68 67
     animation: fadein 0.2s ease;
69
-    margin-left: ($spacing-base / 2);
68
+    background: $color-button-count-background;
69
+    color: $color-button-count-text;
70
+    margin-left: ($spacing-base * 0.75);
70 71
 
71 72
     &::before {
72 73
         border: $arrow-size solid transparent;
73 74
         border-left-width: 0;
74
-        border-right-color: $color-button-background;
75
+        border-right-color: $color-button-count-background;
75 76
         content: '';
76 77
         height: 0;
77 78
         position: absolute;

+ 11
- 0
demo/src/sass/components/header.scss View File

@@ -6,6 +6,13 @@ header {
6 6
     padding-bottom: $spacing-base;
7 7
     text-align: center;
8 8
 
9
+    h1 span {
10
+        animation: shrinkHide 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s forwards;
11
+        display: inline-block;
12
+        font-weight: $font-weight-light;
13
+        opacity: 0.5;
14
+    }
15
+
9 16
     .call-to-action {
10 17
         margin-top: ($spacing-base * 1.5);
11 18
     }
@@ -15,5 +22,9 @@ header {
15 22
         max-width: 360px;
16 23
         padding-bottom: ($spacing-base * 2);
17 24
         text-align: left;
25
+
26
+        p:first-of-type {
27
+            @include font-size($font-size-base + 1);
28
+        }
18 29
     }
19 30
 }

+ 1
- 1
demo/src/sass/components/icons.scss View File

@@ -19,5 +19,5 @@ label svg {
19 19
 
20 20
 a .icon,
21 21
 .btn .icon {
22
-    margin-right: floor($spacing-base / 3);
22
+    margin-right: ($spacing-base / 2);
23 23
 }

+ 0
- 1
demo/src/sass/components/links.scss View File

@@ -12,7 +12,6 @@ button.faux-link {
12 12
 a {
13 13
     border-bottom: 1px dotted currentColor;
14 14
     color: $color-link;
15
-    font-weight: $font-weight-bold;
16 15
     position: relative;
17 16
     text-decoration: none;
18 17
     transition: all 0.2s ease;

+ 2
- 16
demo/src/sass/components/players.scss View File

@@ -2,16 +2,10 @@
2 2
 // Examples
3 3
 // ==========================================================================
4 4
 
5
-// For non supported browsers
6
-video {
7
-    max-width: 100%;
8
-    vertical-align: middle;
9
-}
10
-
11 5
 // Example players
12 6
 .plyr {
13 7
     border-radius: $border-radius-base;
14
-    box-shadow: 0 2px 5px rgba(#000, 0.2);
8
+    box-shadow: 0 2px 15px rgba(#000, 0.1);
15 9
     margin: $spacing-base auto;
16 10
 
17 11
     &.plyr--audio {
@@ -34,17 +28,9 @@ video {
34 28
 
35 29
 // Style full supported player
36 30
 .plyr__cite {
37
-    display: none;
38
-    margin-top: $spacing-base;
31
+    color: $color-gray-5;
39 32
 
40 33
     .icon {
41 34
         margin-right: ceil($spacing-base / 6);
42 35
     }
43 36
 }
44
-
45
-.plyr--video:not(.plyr--youtube):not(.plyr--vimeo) ~ ul .plyr__cite--video,
46
-.plyr--audio ~ ul .plyr__cite--audio,
47
-.plyr--youtube ~ ul .plyr__cite--youtube,
48
-.plyr--vimeo ~ ul .plyr__cite--vimeo {
49
-    display: block;
50
-}

+ 1
- 2
demo/src/sass/layout/core.scss View File

@@ -35,11 +35,10 @@ main {
35 35
 aside {
36 36
     align-items: center;
37 37
     background: #fff;
38
-    color: $gray;
39 38
     display: flex;
40 39
     flex-shrink: 0;
41 40
     justify-content: center;
42
-    padding: ($spacing-base * 0.75);
41
+    padding: $spacing-base;
43 42
     position: relative;
44 43
     text-align: center;
45 44
     text-shadow: none;

+ 14
- 0
demo/src/sass/lib/animation.scss View File

@@ -11,3 +11,17 @@
11 11
         opacity: 1;
12 12
     }
13 13
 }
14
+
15
+@keyframes shrinkHide {
16
+    0% {
17
+        opacity: 0.5;
18
+        width: 38px;
19
+    }
20
+    20% {
21
+        width: 45px;
22
+    }
23
+    100% {
24
+        opacity: 0;
25
+        width: 0;
26
+    }
27
+}

+ 5
- 5
demo/src/sass/lib/fontface.scss View File

@@ -8,7 +8,7 @@
8 8
     font-style: normal;
9 9
     font-weight: $font-weight-light;
10 10
     src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'),
11
-         url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
11
+        url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
12 12
 }
13 13
 
14 14
 @font-face {
@@ -17,7 +17,7 @@
17 17
     font-style: normal;
18 18
     font-weight: $font-weight-regular;
19 19
     src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
20
-         url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
20
+        url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
21 21
 }
22 22
 
23 23
 @font-face {
@@ -26,7 +26,7 @@
26 26
     font-style: normal;
27 27
     font-weight: $font-weight-medium;
28 28
     src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
29
-         url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
29
+        url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
30 30
 }
31 31
 
32 32
 @font-face {
@@ -35,7 +35,7 @@
35 35
     font-style: normal;
36 36
     font-weight: $font-weight-bold;
37 37
     src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'),
38
-         url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
38
+        url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
39 39
 }
40 40
 
41 41
 @font-face {
@@ -44,5 +44,5 @@
44 44
     font-style: normal;
45 45
     font-weight: $font-weight-black;
46 46
     src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'),
47
-         url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
47
+        url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
48 48
 }

+ 1
- 1
demo/src/sass/lib/mixins.scss View File

@@ -36,7 +36,7 @@
36 36
     @return #{$rem}rem;
37 37
 }
38 38
 
39
-@mixin font-size($size: 16) {
39
+@mixin font-size($size: $font-size-base) {
40 40
     font-size: $size * 1px; // Fallback in px
41 41
     font-size: calculate-rem($size);
42 42
 }

+ 26
- 16
demo/src/sass/settings/colors.scss View File

@@ -2,31 +2,41 @@
2 2
 // Colors
3 3
 // ==========================================================================
4 4
 
5
-// Greyscale
6
-$gray-dark: #343f4a;
7
-$gray: #55646b;
8
-$gray-light: #cbd0d3;
9
-$gray-lighter: #dbe3e8;
10
-$off-white: #f2f5f7;
5
+// Grayscale
6
+$color-gray-9: hsl(210, 15%, 16%);
7
+$color-gray-8: lighten($color-gray-9, 9%);
8
+$color-gray-7: lighten($color-gray-8, 9%);
9
+$color-gray-6: lighten($color-gray-7, 9%);
10
+$color-gray-5: lighten($color-gray-6, 9%);
11
+$color-gray-4: lighten($color-gray-5, 9%);
12
+$color-gray-3: lighten($color-gray-4, 9%);
13
+$color-gray-2: lighten($color-gray-3, 9%);
14
+$color-gray-1: lighten($color-gray-2, 9%);
15
+$color-gray-0: lighten($color-gray-1, 9%);
11 16
 
12
-// Text
13
-$color-text: #fff;
17
+// Branding
18
+$color-brand-primary: hsl(198, 100%, 50%);
14 19
 
15
-// Plyr
16
-$color-brand-primary: #1aafff;
20
+// Text
21
+$color-text: $color-gray-7;
22
+$color-headings: $color-brand-primary;
17 23
 
18 24
 // Brands
19 25
 $color-twitter: #4baaf4;
20
-$color-youtube: #cc181e;
21
-$color-vimeo: #19b7ed;
22 26
 
23 27
 // Elements
24
-$color-link: #fff;
25
-$color-background: $color-brand-primary;
28
+$color-link: $color-brand-primary;
29
+
30
+// Background
31
+$color-background-from: hsl(198, 100%, 94%);
32
+$color-background-to: hsl(198, 100%, 98%);
26 33
 
27 34
 // Buttons
28
-$color-button-background: #fff;
29
-$color-button-text: $gray;
35
+$color-button-background: $color-brand-primary;
36
+$color-button-text: #fff;
37
+$color-button-background-hover: hsl(198, 100%, 55%);
38
+$color-button-count-background: #fff;
39
+$color-button-count-text: $color-gray-6;
30 40
 
31 41
 // Focus
32 42
 $tab-focus-default-color: #fff;

+ 1
- 1
demo/src/sass/settings/cosmetic.scss View File

@@ -9,4 +9,4 @@ $arrow-size: 5px;
9 9
 $border-radius-base: 4px;
10 10
 
11 11
 // Background
12
-$page-background: linear-gradient(to left top, lighten($color-background, 10%), darken($color-background, 20%));
12
+$page-background: linear-gradient(to left top, $color-background-from, $color-background-to);

+ 3
- 0
demo/src/sass/settings/plyr.scss View File

@@ -14,6 +14,9 @@ $plyr-font-size-badges: 9px;
14 14
 // Other
15 15
 $plyr-font-smoothing: true;
16 16
 
17
+// Colors
18
+$plyr-color-main: $color-brand-primary;
19
+
17 20
 // Captions
18 21
 $plyr-font-size-captions-base: $plyr-font-size-base;
19 22
 $plyr-font-size-captions-small: $plyr-font-size-small;

+ 1
- 1
demo/src/sass/settings/spacing.scss View File

@@ -2,4 +2,4 @@
2 2
 // Colors
3 3
 // ==========================================================================
4 4
 
5
-$spacing-base: 20px;
5
+$spacing-base: 16px;

+ 1
- 2
demo/src/sass/type/base.scss View File

@@ -14,7 +14,6 @@ body {
14 14
     font-family: $font-sans-serif;
15 15
     font-weight: $font-weight-medium;
16 16
     line-height: $line-height-base;
17
-    text-shadow: 0 1px 1px rgba(#000, 0.15);
18 17
 }
19 18
 
20 19
 button,
@@ -26,7 +25,7 @@ textarea {
26 25
 
27 26
 p,
28 27
 small {
29
-    margin: 0 0 $spacing-base;
28
+    margin: 0 0 ($spacing-base * 1.5);
30 29
 }
31 30
 
32 31
 small {

+ 2
- 1
demo/src/sass/type/headings.scss View File

@@ -4,8 +4,9 @@
4 4
 
5 5
 h1 {
6 6
     @include font-size($font-size-h1);
7
+    color: $color-headings;
7 8
     font-weight: $font-weight-bold;
8 9
     letter-spacing: $letter-spacing-headings;
9 10
     line-height: 1.2;
10
-    margin: 0 0 $spacing-base;
11
+    margin: 0 0 ($spacing-base * 1.5);
11 12
 }

+ 28
- 0
demo/yarn.lock View File

@@ -0,0 +1,28 @@
1
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+# yarn lockfile v1
3
+
4
+
5
+core-js@^3.1.4:
6
+  version "3.1.4"
7
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
8
+  integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
9
+
10
+custom-event-polyfill@^1.0.7:
11
+  version "1.0.7"
12
+  resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
13
+  integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
14
+
15
+raven-js@^3.27.2:
16
+  version "3.27.2"
17
+  resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.27.2.tgz#6c33df952026cd73820aa999122b7b7737a66775"
18
+  integrity sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ==
19
+
20
+shr-buttons@2.0.3:
21
+  version "2.0.3"
22
+  resolved "https://registry.yarnpkg.com/shr-buttons/-/shr-buttons-2.0.3.tgz#2ffd021fc3d789e1510ce2736b938bd09ea1da5a"
23
+  integrity sha512-sPAgHiw4uaIt9TnxTfyZEedDChcldSVtnBHE44cpe/mSC7rqm4IEKZRLYqnVlTcGM+FSDNBPUNpSf50Q2ntd+w==
24
+
25
+url-polyfill@^1.1.5:
26
+  version "1.1.5"
27
+  resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.5.tgz#bec79b72b5407dba6d8cced2e32e4ab273aa9fb1"
28
+  integrity sha512-9XjIJ6nwrU+nGd8t90Ze0Zs7t8A+SU0gqsqPttj6j3zAVe5q0HFcuv37nDBdVSPpi4aTHTfbUF/i+ZVD+o2EbA==

BIN
dist/blank.mp4 View File


+ 1
- 1
dist/plyr.css
File diff suppressed because it is too large
View File


+ 793
- 588
dist/plyr.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.min.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.min.js.map
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.min.mjs
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.min.mjs.map
File diff suppressed because it is too large
View File


+ 793
- 588
dist/plyr.mjs
File diff suppressed because it is too large
View File


+ 5633
- 2934
dist/plyr.polyfilled.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.polyfilled.min.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.polyfilled.min.js.map
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.polyfilled.min.mjs
File diff suppressed because it is too large
View File


+ 1
- 1
dist/plyr.polyfilled.min.mjs.map
File diff suppressed because it is too large
View File


+ 5633
- 2934
dist/plyr.polyfilled.mjs
File diff suppressed because it is too large
View File


+ 31
- 19
gulpfile.js View File

@@ -6,24 +6,28 @@
6 6
 
7 7
 const path = require('path');
8 8
 const gulp = require('gulp');
9
-
9
+// ------------------------------------
10 10
 // JavaScript
11
+// ------------------------------------
11 12
 const terser = require('gulp-terser');
12 13
 const rollup = require('gulp-better-rollup');
13 14
 const babel = require('rollup-plugin-babel');
14 15
 const commonjs = require('rollup-plugin-commonjs');
15 16
 const resolve = require('rollup-plugin-node-resolve');
16
-
17
+// ------------------------------------
17 18
 // CSS
19
+// ------------------------------------
18 20
 const sass = require('gulp-sass');
19 21
 const clean = require('gulp-clean-css');
20 22
 const prefix = require('gulp-autoprefixer');
21
-
23
+// ------------------------------------
22 24
 // Images
25
+// ------------------------------------
23 26
 const svgstore = require('gulp-svgstore');
24 27
 const imagemin = require('gulp-imagemin');
25
-
28
+// ------------------------------------
26 29
 // Utils
30
+// ------------------------------------
27 31
 const del = require('del');
28 32
 const filter = require('gulp-filter');
29 33
 const header = require('gulp-header');
@@ -37,18 +41,22 @@ const plumber = require('gulp-plumber');
37 41
 const size = require('gulp-size');
38 42
 const sourcemaps = require('gulp-sourcemaps');
39 43
 const through = require('through2');
40
-
44
+// ------------------------------------
41 45
 // Deployment
46
+// ------------------------------------
42 47
 const aws = require('aws-sdk');
43 48
 const publish = require('gulp-awspublish');
44 49
 const FastlyPurge = require('fastly-purge');
45
-
50
+// ------------------------------------
51
+// Configs
52
+// ------------------------------------
46 53
 const pkg = require('./package.json');
47 54
 const build = require('./build.json');
48 55
 const deploy = require('./deploy.json');
49
-
56
+// ------------------------------------
57
+// Info from package
58
+// ------------------------------------
50 59
 const { browserslist: browsers, version } = pkg;
51
-
52 60
 const minSuffix = '.min';
53 61
 
54 62
 // Get AWS config
@@ -125,15 +133,16 @@ gulp.task(tasks.clean, done => {
125 133
 
126 134
 // JavaScript
127 135
 Object.entries(build.js).forEach(([filename, entry]) => {
128
-    entry.formats.forEach(format => {
136
+    const { dist, formats, namespace, polyfill, src } = entry;
137
+
138
+    formats.forEach(format => {
129 139
         const name = `js:${filename}:${format}`;
130
-        tasks.js.push(name);
131
-        const polyfill = filename.includes('polyfilled');
132 140
         const extension = format === 'es' ? 'mjs' : 'js';
141
+        tasks.js.push(name);
133 142
 
134 143
         gulp.task(name, () =>
135 144
             gulp
136
-                .src(entry.src)
145
+                .src(src)
137 146
                 .pipe(plumber())
138 147
                 .pipe(sourcemaps.init())
139 148
                 .pipe(
@@ -149,6 +158,7 @@ Object.entries(build.js).forEach(([filename, entry]) => {
149 158
                                             {
150 159
                                                 // debug: true,
151 160
                                                 useBuiltIns: polyfill ? 'usage' : false,
161
+                                                corejs: polyfill ? 3 : undefined,
152 162
                                             },
153 163
                                         ],
154 164
                                     ],
@@ -158,7 +168,7 @@ Object.entries(build.js).forEach(([filename, entry]) => {
158 168
                             ],
159 169
                         },
160 170
                         {
161
-                            name: entry.namespace,
171
+                            name: namespace,
162 172
                             format,
163 173
                         },
164 174
                     ),
@@ -169,25 +179,26 @@ Object.entries(build.js).forEach(([filename, entry]) => {
169 179
                         extname: `.${extension}`,
170 180
                     }),
171 181
                 )
172
-                .pipe(gulp.dest(entry.dist))
182
+                .pipe(gulp.dest(dist))
173 183
                 .pipe(filter(`**/*.${extension}`))
174 184
                 .pipe(terser())
175 185
                 .pipe(rename({ suffix: minSuffix }))
176 186
                 .pipe(size(sizeOptions))
177 187
                 .pipe(sourcemaps.write(''))
178
-                .pipe(gulp.dest(entry.dist)),
188
+                .pipe(gulp.dest(dist)),
179 189
         );
180 190
     });
181 191
 });
182 192
 
183 193
 // CSS
184 194
 Object.entries(build.css).forEach(([filename, entry]) => {
195
+    const { dist, src } = entry;
185 196
     const name = `css:${filename}`;
186 197
     tasks.css.push(name);
187 198
 
188 199
     gulp.task(name, () =>
189 200
         gulp
190
-            .src(entry.src)
201
+            .src(src)
191 202
             .pipe(plumber())
192 203
             .pipe(sass())
193 204
             .pipe(
@@ -197,24 +208,25 @@ Object.entries(build.css).forEach(([filename, entry]) => {
197 208
             )
198 209
             .pipe(clean())
199 210
             .pipe(size(sizeOptions))
200
-            .pipe(gulp.dest(entry.dist)),
211
+            .pipe(gulp.dest(dist)),
201 212
     );
202 213
 });
203 214
 
204 215
 // SVG Sprites
205 216
 Object.entries(build.sprite).forEach(([filename, entry]) => {
217
+    const { dist, src } = entry;
206 218
     const name = `sprite:${filename}`;
207 219
     tasks.sprite.push(name);
208 220
 
209 221
     gulp.task(name, () =>
210 222
         gulp
211
-            .src(entry.src)
223
+            .src(src)
212 224
             .pipe(plumber())
213 225
             .pipe(imagemin())
214 226
             .pipe(svgstore())
215 227
             .pipe(rename({ basename: path.parse(filename).name }))
216 228
             .pipe(size(sizeOptions))
217
-            .pipe(gulp.dest(entry.dist)),
229
+            .pipe(gulp.dest(dist)),
218 230
     );
219 231
 });
220 232
 

+ 1
- 1
license.md View File

@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 18
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 19
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 20
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
-SOFTWARE.
21
+SOFTWARE.

+ 34
- 33
package.json View File

@@ -1,6 +1,6 @@
1 1
 {
2 2
     "name": "plyr",
3
-    "version": "3.5.2",
3
+    "version": "3.5.6",
4 4
     "description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
5 5
     "homepage": "https://plyr.io",
6 6
     "author": "Sam Potts <sam@potts.es>",
@@ -31,31 +31,33 @@
31 31
     "scripts": {
32 32
         "build": "gulp build",
33 33
         "lint": "eslint src/js && npm run-script remark",
34
+        "lint:fix": "eslint --fix src/js",
34 35
         "remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
35 36
         "deploy": "yarn lint && gulp deploy"
36 37
     },
37 38
     "devDependencies": {
38
-        "ansi-colors": "^3.2.4",
39
-        "aws-sdk": "^2.422.0",
40
-        "@babel/core": "^7.3.4",
41
-        "@babel/preset-env": "^7.3.4",
42
-        "babel-eslint": "^10.0.1",
43
-        "del": "^4.0.0",
44
-        "eslint": "^5.15.2",
39
+        "ansi-colors": "^4.0.1",
40
+        "aws-sdk": "^2.478.0",
41
+        "@babel/core": "^7.4.5",
42
+        "@babel/preset-env": "^7.4.5",
43
+        "babel-eslint": "^10.0.2",
44
+        "del": "^4.1.1",
45
+        "eslint": "^5.16.0",
45 46
         "eslint-config-airbnb-base": "^13.1.0",
46
-        "eslint-config-prettier": "^4.1.0",
47
-        "eslint-plugin-import": "^2.16.0",
47
+        "eslint-config-prettier": "^5.0.0",
48
+        "eslint-plugin-import": "^2.17.3",
49
+        "eslint-plugin-simple-import-sort": "^4.0.0",
48 50
         "fancy-log": "^1.3.3",
49 51
         "fastly-purge": "^1.0.1",
50 52
         "git-branch": "^2.0.1",
51
-        "gulp": "^4.0.0",
52
-        "gulp-autoprefixer": "^6.0.0",
53
+        "gulp": "^4.0.2",
54
+        "gulp-autoprefixer": "^6.1.0",
53 55
         "gulp-awspublish": "^4.0.0",
54 56
         "gulp-better-rollup": "^4.0.1",
55
-        "gulp-clean-css": "^4.0.0",
56
-        "gulp-filter": "^5.1.0",
57
+        "gulp-clean-css": "^4.2.0",
58
+        "gulp-filter": "^6.0.0",
57 59
         "gulp-header": "^2.0.7",
58
-        "gulp-imagemin": "^5.0.3",
60
+        "gulp-imagemin": "^6.0.0",
59 61
         "gulp-open": "^3.0.1",
60 62
         "gulp-plumber": "^1.2.1",
61 63
         "gulp-postcss": "^8.0.0",
@@ -65,31 +67,30 @@
65 67
         "gulp-size": "^3.0.0",
66 68
         "gulp-sourcemaps": "^2.6.5",
67 69
         "gulp-svgstore": "^7.0.1",
68
-        "gulp-terser": "^1.1.7",
69
-        "postcss-custom-properties": "^8.0.9",
70
-        "prettier-eslint": "^8.8.2",
70
+        "gulp-terser": "^1.2.0",
71
+        "postcss-custom-properties": "^9.0.1",
72
+        "prettier-eslint": "^9.0.0",
71 73
         "prettier-stylelint": "^0.4.2",
72 74
         "remark-cli": "^6.0.1",
73
-        "remark-validate-links": "^8.0.1",
74
-        "rollup": "^1.6.0",
75
+        "remark-validate-links": "^8.0.3",
76
+        "rollup": "^1.15.6",
75 77
         "rollup-plugin-babel": "^4.3.2",
76
-        "rollup-plugin-commonjs": "^9.2.1",
77
-        "rollup-plugin-node-resolve": "^4.0.1",
78
-        "stylelint": "^9.10.1",
79
-        "stylelint-config-prettier": "^5.0.0",
80
-        "stylelint-config-recommended": "^2.1.0",
81
-        "stylelint-config-sass-guidelines": "^5.3.0",
82
-        "stylelint-order": "^2.1.0",
83
-        "stylelint-scss": "^3.5.4",
84
-        "stylelint-selector-bem-pattern": "^2.0.0",
78
+        "rollup-plugin-commonjs": "^10.0.0",
79
+        "rollup-plugin-node-resolve": "^5.0.3",
80
+        "stylelint": "^10.1.0",
81
+        "stylelint-config-prettier": "^5.2.0",
82
+        "stylelint-config-recommended": "^2.2.0",
83
+        "stylelint-config-sass-guidelines": "^6.0.0",
84
+        "stylelint-order": "^3.0.0",
85
+        "stylelint-scss": "^3.8.0",
86
+        "stylelint-selector-bem-pattern": "^2.1.0",
85 87
         "through2": "^3.0.1"
86 88
     },
87 89
     "dependencies": {
88
-        "core-js": "^2.6.5",
89
-        "custom-event-polyfill": "^1.0.6",
90
-        "loadjs": "^3.6.0",
90
+        "core-js": "^3.1.4",
91
+        "custom-event-polyfill": "^1.0.7",
92
+        "loadjs": "^3.6.1",
91 93
         "rangetouch": "^2.0.0",
92
-        "raven-js": "^3.27.0",
93 94
         "url-polyfill": "^1.1.5"
94 95
     }
95 96
 }

+ 0
- 3
plyr.code-workspace View File

@@ -23,9 +23,6 @@
23 23
         "editor.tabSize": 4,
24 24
         "editor.insertSpaces": true,
25 25
         "editor.formatOnSave": true,
26
-        "editor.codeActionsOnSave": {
27
-            "source.organizeImports": true
28
-        },
29 26
 
30 27
         // Trim on save
31 28
         "files.trimTrailingWhitespace": true

+ 32
- 17
readme.md View File

@@ -6,13 +6,12 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
6 6
 
7 7
 # Features
8 8
 
9
+-   📼 **HTML Video & Audio, YouTube & Vimeo** - support for the major formats
9 10
 -   💪 **Accessible** - full support for VTT captions and screen readers
10 11
 -   🔧 **[Customisable](#html)** - make the player look how you want with the markup you want
11
--   😎 **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
12
+-   😎 **Clean HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
12 13
     `<span>` or `<a href="#">` button hacks
13 14
 -   📱 **Responsive** - works with any screen size
14
--   📼 **HTML Video & Audio** - support for both formats
15
--   📺 **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
16 15
 -   💵 **[Monetization](#ads)** - make money from your videos
17 16
 -   📹 **[Streaming](#demos)** - support for hls.js, Shaka and dash.js streaming playback
18 17
 -   🎛 **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
@@ -25,7 +24,7 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
25 24
 -   📖 **Multiple captions** - support for multiple caption tracks
26 25
 -   🌎 **i18n support** - support for internationalization of controls
27 26
 -   👌 **[Preview thumbnails](#preview-thumbnails)** - support for displaying preview thumbnails
28
--   🤟 **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
27
+-   🤟 **No frameworks** - written in "vanilla" ES6 JavaScript, no jQuery required
29 28
 -   💁‍♀️ **SASS** - to include in your build processes
30 29
 
31 30
 ### Demos
@@ -109,7 +108,15 @@ Or the `<div>` non progressively enhanced method:
109 108
 
110 109
 ## JavaScript
111 110
 
112
-Include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
111
+You can use Plyr as an ES6 module as follows:
112
+
113
+```javascript
114
+import Plyr from 'plyr';
115
+
116
+const player = new Plyr('#player');
117
+```
118
+
119
+Alertnatively you can include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
113 120
 
114 121
 ```html
115 122
 <script src="path/to/plyr.js"></script>
@@ -123,18 +130,18 @@ See [initialising](#initialising) for more information on advanced setups.
123 130
 You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
124 131
 
125 132
 ```html
126
-<script src="https://cdn.plyr.io/3.5.2/plyr.js"></script>
133
+<script src="https://cdn.plyr.io/3.5.6/plyr.js"></script>
127 134
 ```
128 135
 
129 136
 ...or...
130 137
 
131 138
 ```html
132
-<script src="https://cdn.plyr.io/3.5.2/plyr.polyfilled.js"></script>
139
+<script src="https://cdn.plyr.io/3.5.6/plyr.polyfilled.js"></script>
133 140
 ```
134 141
 
135 142
 ## CSS
136 143
 
137
-Include the `plyr.css` stylsheet into your `<head>`
144
+Include the `plyr.css` stylsheet into your `<head>`.
138 145
 
139 146
 ```html
140 147
 <link rel="stylesheet" href="path/to/plyr.css" />
@@ -143,13 +150,13 @@ Include the `plyr.css` stylsheet into your `<head>`
143 150
 If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
144 151
 
145 152
 ```html
146
-<link rel="stylesheet" href="https://cdn.plyr.io/3.5.2/plyr.css" />
153
+<link rel="stylesheet" href="https://cdn.plyr.io/3.5.6/plyr.css" />
147 154
 ```
148 155
 
149 156
 ## SVG Sprite
150 157
 
151 158
 The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
152
-reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.2/plyr.svg`.
159
+reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.6/plyr.svg`.
153 160
 
154 161
 # Ads
155 162
 
@@ -204,7 +211,7 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
204 211
 
205 212
 You can specify a range of arguments for the constructor to use:
206 213
 
207
--   A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
214
+-   A [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
208 215
 -   A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
209 216
 -   A [jQuery](https://jquery.com) object
210 217
 
@@ -212,7 +219,7 @@ _Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element
212 219
 
213 220
 #### Single player
214 221
 
215
-Passing a [string selector](https://developer.mozilla.org/en-US/docs/Web/API/NodeList):
222
+Passing a CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector):
216 223
 
217 224
 ```javascript
218 225
 const player = new Plyr('#player');
@@ -238,7 +245,7 @@ You have two choices here. You can either use a simple array loop to map the con
238 245
 const players = Array.from(document.querySelectorAll('.js-player')).map(p => new Plyr(p));
239 246
 ```
240 247
 
241
-...or use a static method where you can pass a [string selector](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of elements, or a [JQuery](https://jquery.com) object:
248
+...or use a static method where you can pass a [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors), a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement), or a [JQuery](https://jquery.com) object:
242 249
 
243 250
 ```javascript
244 251
 const players = Plyr.setup('.js-player');
@@ -275,7 +282,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
275 282
 | `iconUrl`            | String                     | `null`                                                                                                                         | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info.                                                                                                                                                                                                                                                                                                                     |
276 283
 | `iconPrefix`         | String                     | `plyr`                                                                                                                         | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option.                                                                                                                                                                  |
277 284
 | `blankVideo`         | String                     | `https://cdn.plyr.io/static/blank.mp4`                                                                                         | Specify a URL or path to a blank video file used to properly cancel network requests.                                                                                                                                                                                                                                                                                                                   |
278
-| `autoplay`           | Boolean                    | `false`                                                                                                                        | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true.                                                                                                                                                       |
285
+| `autoplay`&sup2;     | Boolean                    | `false`                                                                                                                        | Autoplay the media on load. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true.                                                                                                                                                                                                                                                         |
279 286
 | `autopause`&sup1;    | Boolean                    | `true`                                                                                                                         | Only allow one player playing at once.                                                                                                                                                                                                                                                                                                                                                                  |
280 287
 | `seekTime`           | Number                     | `10`                                                                                                                           | The time, in seconds, to seek when a user hits fast forward or rewind.                                                                                                                                                                                                                                                                                                                                  |
281 288
 | `volume`             | Number                     | `1`                                                                                                                            | A number, between 0 and 1, representing the initial volume of the player.                                                                                                                                                                                                                                                                                                                               |
@@ -293,7 +300,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
293 300
 | `listeners`          | Object                     | `null`                                                                                                                         | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire.                                                                                                                                                              |
294 301
 | `captions`           | Object                     | `{ active: false, language: 'auto', update: false }`                                                                           | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options).                                                                                        |
295 302
 | `fullscreen`         | Object                     | `{ enabled: true, fallback: true, iosNative: false }`                                                                          | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls)                                                                                                                                                               |
296
-| `ratio`              | String                     | `16:9`                                                                                                                         | The aspect ratio you want to use for embedded players.                                                                                                                                                                                                                                                                                                                                                  |
303
+| `ratio`              | String                     | `null`                                                                                                                         | Force an aspect ratio for all videos. The format is `'w:h'` - e.g. `'16:9'` or `'4:3'`. If this is not specified then the default for HTML5 and Vimeo is to use the native resolution of the video. As dimensions are not available from YouTube via SDK, 16:9 is forced as a sensible default.                                                                                                         |
297 304
 | `storage`            | Object                     | `{ enabled: true, key: 'plyr' }`                                                                                               | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use.                                                                                                                                                                                                                                                                                                               |
298 305
 | `speed`              | Object                     | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }`                                                                 | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5.                                                                                                                                                                                                                                                               |
299 306
 | `quality`            | Object                     | `{ default: 576, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] }`                                           | `default` is the default quality level (if it exists in your sources). `options` are the options to display. This is used to filter the available sources.                                                                                                                                                                                                                                              |
@@ -305,6 +312,11 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
305 312
 | `previewThumbnails`  | Object                     | `{ enabled: false, src: '' }`                                                                                                  | `enabled`: Whether to enable the preview thumbnails (they must be generated by you). `src` must be either a string or an array of strings representing URLs for the VTT files containing the image URL(s). Learn more about [preview thumbnails](#preview-thumbnails) below.                                                                                                                            |
306 313
 
307 314
 1.  Vimeo only
315
+2.  Autoplay is generally not recommended as it is seen as a negative user experience. It is also disabled in many browsers. Before raising issues, do your homework. More info can be found here:
316
+
317
+-   https://webkit.org/blog/6784/new-video-policies-for-ios/
318
+-   https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
319
+-   https://hacks.mozilla.org/2019/02/firefox-66-to-block-automatically-playing-audible-video-and-audio/
308 320
 
309 321
 # API
310 322
 
@@ -405,6 +417,8 @@ player.fullscreen.active; // false;
405 417
 | `fullscreen.active`  | ✓      | -      | Returns a boolean indicating if the current player is in fullscreen mode.                                                                                                                                                                                                                                                              |
406 418
 | `fullscreen.enabled` | ✓      | -      | Returns a boolean indicating if the current player has fullscreen enabled.                                                                                                                                                                                                                                                             |
407 419
 | `pip`&sup1;          | ✓      | ✓      | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+.                                                                                                                                                      |
420
+| `ratio`              | ✓      | ✓      | Gets or sets the video aspect ratio. The setter accepts a string in the same format as the `ratio` option.                                                                                                                                                                                                                             |
421
+| `download`           | ✓      | ✓      | Gets or sets the URL for the download button. The setter accepts a string containing a valid absolute URL.                                                                                                                                                                                                                             |
408 422
 
409 423
 1.  HTML5 only
410 424
 
@@ -648,7 +662,7 @@ The arguments are:
648 662
 -   Provider (`html5`, `youtube` or `vimeo`)
649 663
 -   Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
650 664
 
651
-## Disable support programatically
665
+## Disable support programmatically
652 666
 
653 667
 The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
654 668
 
@@ -696,6 +710,7 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
696 710
 -   [HTML5 Weekly #177](http://html5weekly.com/issues/177)
697 711
 -   [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
698 712
 -   [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
713
+-   [Front End Focus #177](https://frontendfoc.us/issues/177)
699 714
 -   [Hacker News](https://news.ycombinator.com/item?id=9136774)
700 715
 -   [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
701 716
 -   [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
@@ -715,7 +730,7 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
715 730
 -   [Sparkk TV](https://www.sparkktv.com/)
716 731
 -   [@halfhalftravel](https://www.halfhalftravel.com/)
717 732
 
718
-Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
733
+If you want to be added to the list, open a pull request. It'd be awesome to see how you're using Plyr 😎
719 734
 
720 735
 # Useful links and credits
721 736
 

+ 19
- 15
src/js/captions.js View File

@@ -85,7 +85,6 @@ const captions = {
85 85
 
86 86
         const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
87 87
         const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
88
-
89 88
         let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
90 89
 
91 90
         // Use first browser language when language is 'auto'
@@ -124,19 +123,22 @@ const captions = {
124 123
 
125 124
         // Handle tracks (add event listener and "pseudo"-default)
126 125
         if (this.isHTML5 && this.isVideo) {
127
-            tracks.filter(track => !meta.get(track)).forEach(track => {
128
-                this.debug.log('Track added', track);
129
-                // Attempt to store if the original dom element was "default"
130
-                meta.set(track, {
131
-                    default: track.mode === 'showing',
126
+            tracks
127
+                .filter(track => !meta.get(track))
128
+                .forEach(track => {
129
+                    this.debug.log('Track added', track);
130
+                    // Attempt to store if the original dom element was "default"
131
+                    meta.set(track, {
132
+                        default: track.mode === 'showing',
133
+                    });
134
+
135
+                    // Turn off native caption rendering to avoid double captions
136
+                    // eslint-disable-next-line no-param-reassign
137
+                    track.mode = 'hidden';
138
+
139
+                    // Add event listener for cue changes
140
+                    on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
132 141
                 });
133
-
134
-                // Turn off native caption rendering to avoid double captions
135
-                track.mode = 'hidden';
136
-
137
-                // Add event listener for cue changes
138
-                on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
139
-            });
140 142
         }
141 143
 
142 144
         // Update language first time it matches, or if the previous matching track was removed
@@ -164,7 +166,6 @@ const captions = {
164 166
 
165 167
         const { toggled } = this.captions; // Current state
166 168
         const activeClass = this.config.classNames.captions.active;
167
-
168 169
         // Get the next state
169 170
         // If the method is called without parameter, toggle based on current value
170 171
         const active = is.nullOrUndefined(input) ? !toggled : input;
@@ -300,10 +301,12 @@ const captions = {
300 301
         const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
301 302
         const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
302 303
         let track;
304
+
303 305
         languages.every(language => {
304
-            track = sorted.find(track => track.language === language);
306
+            track = sorted.find(t => t.language === language);
305 307
             return !track; // Break iteration if there is a match
306 308
         });
309
+
307 310
         // If no match is found but is required, get first
308 311
         return track || (force ? sorted[0] : undefined);
309 312
     },
@@ -360,6 +363,7 @@ const captions = {
360 363
         // Get cues from track
361 364
         if (!cues) {
362 365
             const track = captions.getCurrentTrack.call(this);
366
+
363 367
             cues = Array.from((track || {}).activeCues || [])
364 368
                 .map(cue => cue.getCueAsHTML())
365 369
                 .map(getHTML);

+ 7
- 13
src/js/config/defaults.js View File

@@ -42,8 +42,9 @@ const defaults = {
42 42
     // Clicking the currentTime inverts it's value to show time left rather than elapsed
43 43
     toggleInvert: true,
44 44
 
45
-    // Aspect ratio (for embeds)
46
-    ratio: '16:9',
45
+    // Force an aspect ratio
46
+    // The format must be `'w:h'` (e.g. `'16:9'`)
47
+    ratio: null,
47 48
 
48 49
     // Click video container to play/pause
49 50
     clickToPlay: true,
@@ -60,7 +61,7 @@ const defaults = {
60 61
     // Sprite (for icons)
61 62
     loadSprite: true,
62 63
     iconPrefix: 'plyr',
63
-    iconUrl: 'https://cdn.plyr.io/3.5.2/plyr.svg',
64
+    iconUrl: 'https://cdn.plyr.io/3.5.6/plyr.svg',
64 65
 
65 66
     // Blank video (used to prevent errors on source change)
66 67
     blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -127,6 +128,7 @@ const defaults = {
127 128
         // 'fast-forward',
128 129
         'progress',
129 130
         'current-time',
131
+        // 'duration',
130 132
         'mute',
131 133
         'volume',
132 134
         'captions',
@@ -194,8 +196,7 @@ const defaults = {
194 196
         },
195 197
         youtube: {
196 198
             sdk: 'https://www.youtube.com/iframe_api',
197
-            api:
198
-                'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet',
199
+            api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
199 200
         },
200 201
         googleIMA: {
201 202
             sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
@@ -319,9 +320,6 @@ const defaults = {
319 320
         progress: '.plyr__progress',
320 321
         captions: '.plyr__captions',
321 322
         caption: '.plyr__caption',
322
-        menu: {
323
-            quality: '.js-plyr__menu__list--quality',
324
-        },
325 323
     },
326 324
 
327 325
     // Class hooks added to the player in different states
@@ -330,6 +328,7 @@ const defaults = {
330 328
         provider: 'plyr--{0}',
331 329
         video: 'plyr__video-wrapper',
332 330
         embed: 'plyr__video-embed',
331
+        videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
333 332
         embedContainer: 'plyr__video-embed__container',
334 333
         poster: 'plyr__poster',
335 334
         posterEnabled: 'plyr__poster-enabled',
@@ -394,11 +393,6 @@ const defaults = {
394 393
         },
395 394
     },
396 395
 
397
-    // API keys
398
-    keys: {
399
-        google: null,
400
-    },
401
-
402 396
     // Advertisements plugin
403 397
     // Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
404 398
     ads: {

+ 305
- 271
src/js/controls.js View File

@@ -4,6 +4,7 @@
4 4
 // ==========================================================================
5 5
 
6 6
 import RangeTouch from 'rangetouch';
7
+
7 8
 import captions from './captions';
8 9
 import html5 from './html5';
9 10
 import support from './support';
@@ -27,7 +28,7 @@ import {
27 28
 import { off, on } from './utils/events';
28 29
 import i18n from './utils/i18n';
29 30
 import is from './utils/is';
30
-import loadSprite from './utils/loadSprite';
31
+import loadSprite from './utils/load-sprite';
31 32
 import { extend } from './utils/objects';
32 33
 import { getPercentage, replaceAll, toCamelCase, toTitleCase } from './utils/strings';
33 34
 import { formatTime, getHours } from './utils/time';
@@ -105,7 +106,6 @@ const controls = {
105 106
         const namespace = 'http://www.w3.org/2000/svg';
106 107
         const iconUrl = controls.getIconUrl.call(this);
107 108
         const iconPath = `${!iconUrl.cors ? iconUrl.url : ''}#${this.config.iconPrefix}`;
108
-
109 109
         // Create <svg>
110 110
         const icon = document.createElementNS(namespace, 'svg');
111 111
         setAttributes(
@@ -172,7 +172,7 @@ const controls = {
172 172
 
173 173
     // Create a <button>
174 174
     createButton(buttonType, attr) {
175
-        const attributes = Object.assign({}, attr);
175
+        const attributes = extend({}, attr);
176 176
         let type = toCamelCase(buttonType);
177 177
 
178 178
         const props = {
@@ -198,8 +198,10 @@ const controls = {
198 198
 
199 199
         // Set class name
200 200
         if (Object.keys(attributes).includes('class')) {
201
-            if (!attributes.class.includes(this.config.classNames.control)) {
202
-                attributes.class += ` ${this.config.classNames.control}`;
201
+            if (!attributes.class.split(' ').some(c => c === this.config.classNames.control)) {
202
+                extend(attributes, {
203
+                    class: `${attributes.class} ${this.config.classNames.control}`,
204
+                });
203 205
             }
204 206
         } else {
205 207
             attributes.class = this.config.classNames.control;
@@ -377,13 +379,13 @@ const controls = {
377 379
     },
378 380
 
379 381
     // Create time display
380
-    createTime(type) {
381
-        const attributes = getAttributesFromSelector(this.config.selectors.display[type]);
382
+    createTime(type, attrs) {
383
+        const attributes = getAttributesFromSelector(this.config.selectors.display[type], attrs);
382 384
 
383 385
         const container = createElement(
384 386
             'div',
385 387
             extend(attributes, {
386
-                class: `${this.config.classNames.display.time} ${attributes.class ? attributes.class : ''}`.trim(),
388
+                class: `${attributes.class ? attributes.class : ''} ${this.config.classNames.display.time} `.trim(),
387 389
                 'aria-label': i18n.get(type, this.config),
388 390
             }),
389 391
             '00:00',
@@ -491,15 +493,15 @@ const controls = {
491 493
             get() {
492 494
                 return menuItem.getAttribute('aria-checked') === 'true';
493 495
             },
494
-            set(checked) {
496
+            set(check) {
495 497
                 // Ensure exclusivity
496
-                if (checked) {
498
+                if (check) {
497 499
                     Array.from(menuItem.parentNode.children)
498 500
                         .filter(node => matches(node, '[role="menuitemradio"]'))
499 501
                         .forEach(node => node.setAttribute('aria-checked', 'false'));
500 502
                 }
501 503
 
502
-                menuItem.setAttribute('aria-checked', checked ? 'true' : 'false');
504
+                menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
503 505
             },
504 506
         });
505 507
 
@@ -607,17 +609,17 @@ const controls = {
607 609
         let value = 0;
608 610
 
609 611
         const setProgress = (target, input) => {
610
-            const value = is.number(input) ? input : 0;
612
+            const val = is.number(input) ? input : 0;
611 613
             const progress = is.element(target) ? target : this.elements.display.buffer;
612 614
 
613 615
             // Update value and label
614 616
             if (is.element(progress)) {
615
-                progress.value = value;
617
+                progress.value = val;
616 618
 
617 619
                 // Update text label inside
618 620
                 const label = progress.getElementsByTagName('span')[0];
619 621
                 if (is.element(label)) {
620
-                    label.childNodes[0].nodeValue = value;
622
+                    label.childNodes[0].nodeValue = val;
621 623
                 }
622 624
             }
623 625
         };
@@ -699,14 +701,8 @@ const controls = {
699 701
             return;
700 702
         }
701 703
 
702
-        // Calculate percentage
703
-        let percent = 0;
704
-        const clientRect = this.elements.progress.getBoundingClientRect();
705 704
         const visible = `${this.config.classNames.tooltip}--visible`;
706
-
707
-        const toggle = toggle => {
708
-            toggleClass(this.elements.display.seekTooltip, visible, toggle);
709
-        };
705
+        const toggle = show => toggleClass(this.elements.display.seekTooltip, visible, show);
710 706
 
711 707
         // Hide on touch
712 708
         if (this.touch) {
@@ -715,6 +711,9 @@ const controls = {
715 711
         }
716 712
 
717 713
         // Determine percentage, if already visible
714
+        let percent = 0;
715
+        const clientRect = this.elements.progress.getBoundingClientRect();
716
+
718 717
         if (is.event(event)) {
719 718
             percent = (100 / clientRect.width) * (event.pageX - clientRect.left);
720 719
         } else if (hasClass(this.elements.display.seekTooltip, visible)) {
@@ -1111,7 +1110,7 @@ const controls = {
1111 1110
         let target = pane;
1112 1111
 
1113 1112
         if (!is.element(target)) {
1114
-            target = Object.values(this.elements.settings.panels).find(pane => !pane.hidden);
1113
+            target = Object.values(this.elements.settings.panels).find(p => !p.hidden);
1115 1114
         }
1116 1115
 
1117 1116
         const firstItem = target.querySelector('[role^="menuitem"]');
@@ -1138,7 +1137,10 @@ const controls = {
1138 1137
         } else if (is.keyboardEvent(input) && input.which === 27) {
1139 1138
             show = false;
1140 1139
         } else if (is.event(input)) {
1141
-            const isMenuItem = popup.contains(input.target);
1140
+            // If Plyr is in a shadowDOM, the event target is set to the component, instead of the
1141
+            // Element in the shadowDOM. The path, if available, is complete.
1142
+            const target = is.function(input.composedPath) ? input.composedPath()[0] : input.target;
1143
+            const isMenuItem = popup.contains(target);
1142 1144
 
1143 1145
             // If the click was inside the menu or if the click
1144 1146
             // wasn't the button or menu item and we're trying to
@@ -1191,7 +1193,7 @@ const controls = {
1191 1193
 
1192 1194
     // Show a panel in the menu
1193 1195
     showMenuPanel(type = '', tabFocus = false) {
1194
-        const target = document.getElementById(`plyr-settings-${this.id}-${type}`);
1196
+        const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`);
1195 1197
 
1196 1198
         // Nothing to show, bail
1197 1199
         if (!is.element(target)) {
@@ -1244,8 +1246,8 @@ const controls = {
1244 1246
         controls.focusFirstMenuItem.call(this, target, tabFocus);
1245 1247
     },
1246 1248
 
1247
-    // Set the download link
1248
-    setDownloadLink() {
1249
+    // Set the download URL
1250
+    setDownloadUrl() {
1249 1251
         const button = this.elements.buttons.download;
1250 1252
 
1251 1253
         // Bail if no button
@@ -1253,324 +1255,356 @@ const controls = {
1253 1255
             return;
1254 1256
         }
1255 1257
 
1256
-        // Set download link
1258
+        // Set attribute
1257 1259
         button.setAttribute('href', this.download);
1258 1260
     },
1259 1261
 
1260 1262
     // Build the default HTML
1261
-    // TODO: Set order based on order in the config.controls array?
1262 1263
     create(data) {
1263
-        // Create the container
1264
-        const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));
1264
+        const {
1265
+            bindMenuItemShortcuts,
1266
+            createButton,
1267
+            createProgress,
1268
+            createRange,
1269
+            createTime,
1270
+            setQualityMenu,
1271
+            setSpeedMenu,
1272
+            showMenuPanel,
1273
+        } = controls;
1274
+        this.elements.controls = null;
1265 1275
 
1266
-        // Restart button
1267
-        if (this.config.controls.includes('restart')) {
1268
-            container.appendChild(controls.createButton.call(this, 'restart'));
1276
+        // Larger overlaid play button
1277
+        if (this.config.controls.includes('play-large')) {
1278
+            this.elements.container.appendChild(createButton.call(this, 'play-large'));
1269 1279
         }
1270 1280
 
1271
-        // Rewind button
1272
-        if (this.config.controls.includes('rewind')) {
1273
-            container.appendChild(controls.createButton.call(this, 'rewind'));
1274
-        }
1281
+        // Create the container
1282
+        const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));
1283
+        this.elements.controls = container;
1275 1284
 
1276
-        // Play/Pause button
1277
-        if (this.config.controls.includes('play')) {
1278
-            container.appendChild(controls.createButton.call(this, 'play'));
1279
-        }
1285
+        // Default item attributes
1286
+        const defaultAttributes = { class: 'plyr__controls__item' };
1280 1287
 
1281
-        // Fast forward button
1282
-        if (this.config.controls.includes('fast-forward')) {
1283
-            container.appendChild(controls.createButton.call(this, 'fast-forward'));
1284
-        }
1288
+        // Loop through controls in order
1289
+        dedupe(this.config.controls).forEach(control => {
1290
+            // Restart button
1291
+            if (control === 'restart') {
1292
+                container.appendChild(createButton.call(this, 'restart', defaultAttributes));
1293
+            }
1285 1294
 
1286
-        // Progress
1287
-        if (this.config.controls.includes('progress')) {
1288
-            const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
1295
+            // Rewind button
1296
+            if (control === 'rewind') {
1297
+                container.appendChild(createButton.call(this, 'rewind', defaultAttributes));
1298
+            }
1289 1299
 
1290
-            // Seek range slider
1291
-            progress.appendChild(
1292
-                controls.createRange.call(this, 'seek', {
1293
-                    id: `plyr-seek-${data.id}`,
1294
-                }),
1295
-            );
1300
+            // Play/Pause button
1301
+            if (control === 'play') {
1302
+                container.appendChild(createButton.call(this, 'play', defaultAttributes));
1303
+            }
1304
+
1305
+            // Fast forward button
1306
+            if (control === 'fast-forward') {
1307
+                container.appendChild(createButton.call(this, 'fast-forward', defaultAttributes));
1308
+            }
1296 1309
 
1297
-            // Buffer progress
1298
-            progress.appendChild(controls.createProgress.call(this, 'buffer'));
1310
+            // Progress
1311
+            if (control === 'progress') {
1312
+                const progressContainer = createElement('div', {
1313
+                    class: `${defaultAttributes.class} plyr__progress__container`,
1314
+                });
1299 1315
 
1300
-            // TODO: Add loop display indicator
1316
+                const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));
1301 1317
 
1302
-            // Seek tooltip
1303
-            if (this.config.tooltips.seek) {
1304
-                const tooltip = createElement(
1305
-                    'span',
1306
-                    {
1307
-                        class: this.config.classNames.tooltip,
1308
-                    },
1309
-                    '00:00',
1318
+                // Seek range slider
1319
+                progress.appendChild(
1320
+                    createRange.call(this, 'seek', {
1321
+                        id: `plyr-seek-${data.id}`,
1322
+                    }),
1310 1323
                 );
1311 1324
 
1312
-                progress.appendChild(tooltip);
1313
-                this.elements.display.seekTooltip = tooltip;
1314
-            }
1325
+                // Buffer progress
1326
+                progress.appendChild(createProgress.call(this, 'buffer'));
1315 1327
 
1316
-            this.elements.progress = progress;
1317
-            container.appendChild(this.elements.progress);
1318
-        }
1328
+                // TODO: Add loop display indicator
1319 1329
 
1320
-        // Media current time display
1321
-        if (this.config.controls.includes('current-time')) {
1322
-            container.appendChild(controls.createTime.call(this, 'currentTime'));
1323
-        }
1324
-
1325
-        // Media duration display
1326
-        if (this.config.controls.includes('duration')) {
1327
-            container.appendChild(controls.createTime.call(this, 'duration'));
1328
-        }
1330
+                // Seek tooltip
1331
+                if (this.config.tooltips.seek) {
1332
+                    const tooltip = createElement(
1333
+                        'span',
1334
+                        {
1335
+                            class: this.config.classNames.tooltip,
1336
+                        },
1337
+                        '00:00',
1338
+                    );
1329 1339
 
1330
-        // Volume controls
1331
-        if (this.config.controls.includes('mute') || this.config.controls.includes('volume')) {
1332
-            const volume = createElement('div', {
1333
-                class: 'plyr__volume',
1334
-            });
1340
+                    progress.appendChild(tooltip);
1341
+                    this.elements.display.seekTooltip = tooltip;
1342
+                }
1335 1343
 
1336
-            // Toggle mute button
1337
-            if (this.config.controls.includes('mute')) {
1338
-                volume.appendChild(controls.createButton.call(this, 'mute'));
1344
+                this.elements.progress = progress;
1345
+                progressContainer.appendChild(this.elements.progress);
1346
+                container.appendChild(progressContainer);
1339 1347
             }
1340 1348
 
1341
-            // Volume range control
1342
-            if (this.config.controls.includes('volume')) {
1343
-                // Set the attributes
1344
-                const attributes = {
1345
-                    max: 1,
1346
-                    step: 0.05,
1347
-                    value: this.config.volume,
1348
-                };
1349
-
1350
-                // Create the volume range slider
1351
-                volume.appendChild(
1352
-                    controls.createRange.call(
1353
-                        this,
1354
-                        'volume',
1355
-                        extend(attributes, {
1356
-                            id: `plyr-volume-${data.id}`,
1357
-                        }),
1358
-                    ),
1359
-                );
1360
-
1361
-                this.elements.volume = volume;
1349
+            // Media current time display
1350
+            if (control === 'current-time') {
1351
+                container.appendChild(createTime.call(this, 'currentTime', defaultAttributes));
1362 1352
             }
1363 1353
 
1364
-            container.appendChild(volume);
1365
-        }
1366
-
1367
-        // Toggle captions button
1368
-        if (this.config.controls.includes('captions')) {
1369
-            container.appendChild(controls.createButton.call(this, 'captions'));
1370
-        }
1354
+            // Media duration display
1355
+            if (control === 'duration') {
1356
+                container.appendChild(createTime.call(this, 'duration', defaultAttributes));
1357
+            }
1371 1358
 
1372
-        // Settings button / menu
1373
-        if (this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
1374
-            const control = createElement('div', {
1375
-                class: 'plyr__menu',
1376
-                hidden: '',
1377
-            });
1359
+            // Volume controls
1360
+            if (control === 'mute' || control === 'volume') {
1361
+                let { volume } = this.elements;
1378 1362
 
1379
-            control.appendChild(
1380
-                controls.createButton.call(this, 'settings', {
1381
-                    'aria-haspopup': true,
1382
-                    'aria-controls': `plyr-settings-${data.id}`,
1383
-                    'aria-expanded': false,
1384
-                }),
1385
-            );
1363
+                // Create the volume container if needed
1364
+                if (!is.element(volume) || !container.contains(volume)) {
1365
+                    volume = createElement(
1366
+                        'div',
1367
+                        extend({}, defaultAttributes, {
1368
+                            class: `${defaultAttributes.class} plyr__volume`.trim(),
1369
+                        }),
1370
+                    );
1386 1371
 
1387
-            const popup = createElement('div', {
1388
-                class: 'plyr__menu__container',
1389
-                id: `plyr-settings-${data.id}`,
1390
-                hidden: '',
1391
-            });
1372
+                    this.elements.volume = volume;
1392 1373
 
1393
-            const inner = createElement('div');
1374
+                    container.appendChild(volume);
1375
+                }
1394 1376
 
1395
-            const home = createElement('div', {
1396
-                id: `plyr-settings-${data.id}-home`,
1397
-            });
1377
+                // Toggle mute button
1378
+                if (control === 'mute') {
1379
+                    volume.appendChild(createButton.call(this, 'mute'));
1380
+                }
1398 1381
 
1399
-            // Create the menu
1400
-            const menu = createElement('div', {
1401
-                role: 'menu',
1402
-            });
1382
+                // Volume range control
1383
+                if (control === 'volume') {
1384
+                    // Set the attributes
1385
+                    const attributes = {
1386
+                        max: 1,
1387
+                        step: 0.05,
1388
+                        value: this.config.volume,
1389
+                    };
1390
+
1391
+                    // Create the volume range slider
1392
+                    volume.appendChild(
1393
+                        createRange.call(
1394
+                            this,
1395
+                            'volume',
1396
+                            extend(attributes, {
1397
+                                id: `plyr-volume-${data.id}`,
1398
+                            }),
1399
+                        ),
1400
+                    );
1401
+                }
1402
+            }
1403 1403
 
1404
-            home.appendChild(menu);
1405
-            inner.appendChild(home);
1406
-            this.elements.settings.panels.home = home;
1404
+            // Toggle captions button
1405
+            if (control === 'captions') {
1406
+                container.appendChild(createButton.call(this, 'captions', defaultAttributes));
1407
+            }
1407 1408
 
1408
-            // Build the menu items
1409
-            this.config.settings.forEach(type => {
1410
-                // TODO: bundle this with the createMenuItem helper and bindings
1411
-                const menuItem = createElement(
1412
-                    'button',
1413
-                    extend(getAttributesFromSelector(this.config.selectors.buttons.settings), {
1414
-                        type: 'button',
1415
-                        class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
1416
-                        role: 'menuitem',
1417
-                        'aria-haspopup': true,
1409
+            // Settings button / menu
1410
+            if (control === 'settings' && !is.empty(this.config.settings)) {
1411
+                const wrapper = createElement(
1412
+                    'div',
1413
+                    extend({}, defaultAttributes, {
1414
+                        class: `${defaultAttributes.class} plyr__menu`.trim(),
1418 1415
                         hidden: '',
1419 1416
                     }),
1420 1417
                 );
1421 1418
 
1422
-                // Bind menu shortcuts for keyboard users
1423
-                controls.bindMenuItemShortcuts.call(this, menuItem, type);
1419
+                wrapper.appendChild(
1420
+                    createButton.call(this, 'settings', {
1421
+                        'aria-haspopup': true,
1422
+                        'aria-controls': `plyr-settings-${data.id}`,
1423
+                        'aria-expanded': false,
1424
+                    }),
1425
+                );
1424 1426
 
1425
-                // Show menu on click
1426
-                on(menuItem, 'click', () => {
1427
-                    controls.showMenuPanel.call(this, type, false);
1427
+                const popup = createElement('div', {
1428
+                    class: 'plyr__menu__container',
1429
+                    id: `plyr-settings-${data.id}`,
1430
+                    hidden: '',
1428 1431
                 });
1429 1432
 
1430
-                const flex = createElement('span', null, i18n.get(type, this.config));
1433
+                const inner = createElement('div');
1431 1434
 
1432
-                const value = createElement('span', {
1433
-                    class: this.config.classNames.menu.value,
1435
+                const home = createElement('div', {
1436
+                    id: `plyr-settings-${data.id}-home`,
1434 1437
                 });
1435 1438
 
1436
-                // Speed contains HTML entities
1437
-                value.innerHTML = data[type];
1439
+                // Create the menu
1440
+                const menu = createElement('div', {
1441
+                    role: 'menu',
1442
+                });
1438 1443
 
1439
-                flex.appendChild(value);
1440
-                menuItem.appendChild(flex);
1441
-                menu.appendChild(menuItem);
1444
+                home.appendChild(menu);
1445
+                inner.appendChild(home);
1446
+                this.elements.settings.panels.home = home;
1447
+
1448
+                // Build the menu items
1449
+                this.config.settings.forEach(type => {
1450
+                    // TODO: bundle this with the createMenuItem helper and bindings
1451
+                    const menuItem = createElement(
1452
+                        'button',
1453
+                        extend(getAttributesFromSelector(this.config.selectors.buttons.settings), {
1454
+                            type: 'button',
1455
+                            class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,
1456
+                            role: 'menuitem',
1457
+                            'aria-haspopup': true,
1458
+                            hidden: '',
1459
+                        }),
1460
+                    );
1442 1461
 
1443
-                // Build the panes
1444
-                const pane = createElement('div', {
1445
-                    id: `plyr-settings-${data.id}-${type}`,
1446
-                    hidden: '',
1447
-                });
1462
+                    // Bind menu shortcuts for keyboard users
1463
+                    bindMenuItemShortcuts.call(this, menuItem, type);
1448 1464
 
1449
-                // Back button
1450
-                const backButton = createElement('button', {
1451
-                    type: 'button',
1452
-                    class: `${this.config.classNames.control} ${this.config.classNames.control}--back`,
1453
-                });
1465
+                    // Show menu on click
1466
+                    on(menuItem, 'click', () => {