Controls cleanup, work on captions bug, click to invert time
This commit is contained in:
parent
d7a1c44281
commit
6984d6fb16
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.js
vendored
2
demo/dist/demo.js
vendored
@ -1,3 +1,3 @@
|
|||||||
document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,a){if(t in n&&(a||t!==r)&&(r.length||t!==n.video)){switch(t){case n.video:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case n.audio:o.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case n.youtube:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",type:"youtube"}]};break;case n.vimeo:o.source={type:"video",sources:[{src:"https://vimeo.com/76979871",type:"vimeo"}]}}r=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"]});window.player=o;var i=document.querySelectorAll("[data-source]"),n={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),a&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var s=!r.length;s&&(r=n.video),r in n&&window.history.replaceState({type:r},"",s?"":"#"+r),r!==n.video&&t(r,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,n,r,a){e.GoogleAnalyticsObject=n,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"));
|
document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,r){if(t in a&&(r||t!==n)&&(n.length||t!==a.video)){switch(t){case a.video:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"media/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"hmedia/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"media/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case a.audio:o.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case a.youtube:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",type:"youtube"}]};break;case a.vimeo:o.source={type:"video",sources:[{src:"https://vimeo.com/76979871",type:"vimeo"}]}}n=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"],keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"}});window.player=o;var i=document.querySelectorAll("[data-source]"),a={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},n=window.location.hash.replace("#",""),r=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),r&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),r){var s=!n.length;s&&(n=a.video),n in a&&window.history.replaceState({type:n},"",s?"":"#"+n),n!==a.video&&t(n,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,a,n,r){e.GoogleAnalyticsObject=a,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,n=t.createElement(o),r=t.getElementsByTagName(o)[0],n.async=1,n.src="//www.google-analytics.com/analytics.js",r.parentNode.insertBefore(n,r)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"));
|
||||||
|
|
||||||
//# sourceMappingURL=demo.js.map
|
//# sourceMappingURL=demo.js.map
|
||||||
|
2
demo/dist/demo.js.map
vendored
2
demo/dist/demo.js.map
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/error.css
vendored
2
demo/dist/error.css
vendored
File diff suppressed because one or more lines are too long
@ -82,17 +82,16 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<video controls crossorigin playsinline loop poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
|
<video controls crossorigin playsinline loop poster="media/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
||||||
id="player">
|
|
||||||
<!-- Video files -->
|
<!-- Video files -->
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
<source src="media/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
||||||
|
|
||||||
<!-- Text track file -->
|
<!-- Text track file -->
|
||||||
<track kind="captions" label="English" srclang="en" src="webvtt/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
|
<track kind="captions" label="English" srclang="en" src="media/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
|
||||||
<track kind="captions" label="Français" srclang="fr" src="webvtt/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
<track kind="captions" label="Français" srclang="fr" src="media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
||||||
|
|
||||||
<!-- Fallback for browsers that don't support the <video> element -->
|
<!-- Fallback for browsers that don't support the <video> element -->
|
||||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
<a href="media/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
BIN
demo/media/View_From_A_Blue_Moon_Trailer-HD.jpg
Normal file
BIN
demo/media/View_From_A_Blue_Moon_Trailer-HD.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
@ -65,6 +65,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
'pip',
|
'pip',
|
||||||
'airplay',
|
'airplay',
|
||||||
],
|
],
|
||||||
|
keys: {
|
||||||
|
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expose for testing
|
// Expose for testing
|
||||||
@ -102,24 +105,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
title: 'View From A Blue Moon',
|
title: 'View From A Blue Moon',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
src: 'media/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
poster: 'hmedia/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||||
tracks: [
|
tracks: [
|
||||||
{
|
{
|
||||||
kind: 'captions',
|
kind: 'captions',
|
||||||
label: 'English',
|
label: 'English',
|
||||||
srclang: 'en',
|
srclang: 'en',
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
src: 'media/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'captions',
|
kind: 'captions',
|
||||||
label: 'French',
|
label: 'French',
|
||||||
srclang: 'fr',
|
srclang: 'fr',
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
src: 'media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ video {
|
|||||||
|
|
||||||
// Example players
|
// Example players
|
||||||
.plyr {
|
.plyr {
|
||||||
margin: 0 auto;
|
margin: @spacing-base auto;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
box-shadow: 0 2px 5px fade(#000, 20%);
|
box-shadow: 0 2px 5px fade(#000, 20%);
|
||||||
}
|
}
|
||||||
|
@ -27,15 +27,18 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
margin: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Avenir';
|
font-family: 'Avenir';
|
||||||
src: url('https://cdn.plyr.io/static/fonts/avenir-medium.woff2') format('woff2'),
|
src: local('Avenir-Medium'), url('https://cdn.plyr.io/static/fonts/avenir-medium.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/avenir-medium.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/avenir-medium.woff') format('woff');
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: @font-weight-base;
|
font-weight: @font-weight-base;
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Avenir';
|
font-family: 'Avenir';
|
||||||
src: url('https://cdn.plyr.io/static/fonts/avenir-bold.woff2') format('woff2'),
|
src: local('Avenir-Heavy'), url('https://cdn.plyr.io/static/fonts/avenir-bold.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/avenir-bold.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/avenir-bold.woff') format('woff');
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: @font-weight-bold;
|
font-weight: @font-weight-bold;
|
||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Avenir';
|
font-family: 'Avenir';
|
||||||
src: url('https://cdn.plyr.io/static/fonts/avenir-black.woff2?v=3') format('woff2'),
|
src: local('Avenir-Black'), url('https://cdn.plyr.io/static/fonts/avenir-black.woff2?v=3') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/avenir-black.woff?v=3') format('woff');
|
url('https://cdn.plyr.io/static/fonts/avenir-black.woff?v=3') format('woff');
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: @font-weight-heavy;
|
font-weight: @font-weight-heavy;
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
.font-size(@font-size: 16) {
|
.font-size(@font-size: 16) {
|
||||||
@rem: round((@font-size / 16), 3);
|
@rem: round((@font-size / 16), 3);
|
||||||
|
|
||||||
font-size: (@font-size * 1px);
|
font-size: (@font-size * 1px);
|
||||||
font-size: ~'@{rem}rem';
|
font-size: ~'@{rem}rem';
|
||||||
}
|
}
|
||||||
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js
vendored
2
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
@ -233,7 +233,7 @@ Option | Type | Default | Description
|
|||||||
`seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind.
|
`seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind.
|
||||||
`volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player.
|
`volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player.
|
||||||
`muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true.
|
`muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true.
|
||||||
`clickToPlay` | Boolean | `true` | Click (or tap) of the video container will toggle pause/play.
|
`clickToPlay` | Boolean | `true` | Click (or tap) of the video container will toggle play/pause.
|
||||||
`disableContextMenu` | Boolean | `true` | Disable right click menu on video to <em>help</em> as very primitive obfuscation to prevent downloads of content.
|
`disableContextMenu` | Boolean | `true` | Disable right click menu on video to <em>help</em> as very primitive obfuscation to prevent downloads of content.
|
||||||
`hideControls` | Boolean | `true` | Hide video controls automatically after 2s of no mouse or focus movement, on control element blur (tab out), on playback start or entering fullscreen. As soon as the mouse is moved, a control element is focused or playback is paused, the controls reappear instantly.
|
`hideControls` | Boolean | `true` | Hide video controls automatically after 2s of no mouse or focus movement, on control element blur (tab out), on playback start or entering fullscreen. As soon as the mouse is moved, a control element is focused or playback is paused, the controls reappear instantly.
|
||||||
`showPosterOnEnd` | Boolean | false | This will restore and *reload* HTML5 video once playback is complete. Note: depending on the browser caching, this may result in the video downloading again (or parts of it). Use with caution.
|
`showPosterOnEnd` | Boolean | false | This will restore and *reload* HTML5 video once playback is complete. Note: depending on the browser caching, this may result in the video downloading again (or parts of it). Use with caution.
|
||||||
@ -241,9 +241,12 @@ Option | Type | Default | Description
|
|||||||
`tooltips` | Object | `{ controls: false, seek: true }` | `controls`: Display control labels as tooltips on `:hover` & `:focus` (by default, the labels are screen reader only). `seek`: Display a seek tooltip to indicate on click where the media would seek to.
|
`tooltips` | Object | `{ controls: false, seek: true }` | `controls`: Display control labels as tooltips on `:hover` & `:focus` (by default, the labels are screen reader only). `seek`: Display a seek tooltip to indicate on click where the media would seek to.
|
||||||
`duration` | Number | `null` | Specify a custom duration for media.
|
`duration` | Number | `null` | Specify a custom duration for media.
|
||||||
`displayDuration` | Boolean | `true` | Displays the duration of the media on the "metadataloaded" event (on startup) in the current time display. This will only work if the `preload` attribute is not set to `none` (or is not set at all) and you choose not to display the duration (see `controls` option).
|
`displayDuration` | Boolean | `true` | Displays the duration of the media on the "metadataloaded" event (on startup) in the current time display. This will only work if the `preload` attribute is not set to `none` (or is not set at all) and you choose not to display the duration (see `controls` option).
|
||||||
|
`invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter.
|
||||||
|
`toggleInvert` | Boolean | `true` | Allow users to click to toggle the above.
|
||||||
`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, the default handler will not fire.
|
`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, the default handler will not fire.
|
||||||
`captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available).
|
`captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available).
|
||||||
`fullscreen` | Object | `{ enabled: true, fallback: true }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution.
|
`fullscreen` | Object | `{ enabled: true, fallback: true }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution.
|
||||||
|
`ratio` | String | `16:9` | The aspect ratio you want to use for embedded players.
|
||||||
`storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use.
|
`storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use.
|
||||||
`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.
|
`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.
|
||||||
`quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display.
|
`quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display.
|
||||||
|
@ -79,8 +79,9 @@ const captions = {
|
|||||||
|
|
||||||
// Filter doesn't seem to work for a TextTrackList :-(
|
// Filter doesn't seem to work for a TextTrackList :-(
|
||||||
Array.from(this.captions.tracks).forEach(track => {
|
Array.from(this.captions.tracks).forEach(track => {
|
||||||
if (track.language === this.captions.language.toLowerCase()) {
|
if (track.language.toLowerCase() === this.language.toLowerCase()) {
|
||||||
this.captions.currentTrack = track;
|
this.captions.currentTrack = track;
|
||||||
|
console.warn(`Set current track to ${this.language}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
171
src/js/controls.js
vendored
171
src/js/controls.js
vendored
@ -115,6 +115,10 @@ const controls = {
|
|||||||
|
|
||||||
// Create a badge
|
// Create a badge
|
||||||
createBadge(text) {
|
createBadge(text) {
|
||||||
|
if (utils.is.empty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const badge = utils.createElement('span', {
|
const badge = utils.createElement('span', {
|
||||||
class: this.config.classNames.menu.value,
|
class: this.config.classNames.menu.value,
|
||||||
});
|
});
|
||||||
@ -319,6 +323,39 @@ const controls = {
|
|||||||
return container;
|
return container;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Create a settings menu item
|
||||||
|
createMenuItem(value, list, type, title, badge = null, checked = false) {
|
||||||
|
const item = utils.createElement('li');
|
||||||
|
|
||||||
|
const label = utils.createElement('label', {
|
||||||
|
class: this.config.classNames.control,
|
||||||
|
});
|
||||||
|
|
||||||
|
const radio = utils.createElement(
|
||||||
|
'input',
|
||||||
|
utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), {
|
||||||
|
type: 'radio',
|
||||||
|
name: `plyr-${type}`,
|
||||||
|
value,
|
||||||
|
checked,
|
||||||
|
class: 'plyr__sr-only',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const faux = utils.createElement('span', { 'aria-hidden': true });
|
||||||
|
|
||||||
|
label.appendChild(radio);
|
||||||
|
label.appendChild(faux);
|
||||||
|
label.insertAdjacentHTML('beforeend', title);
|
||||||
|
|
||||||
|
if (utils.is.htmlElement(badge)) {
|
||||||
|
label.appendChild(badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.appendChild(label);
|
||||||
|
list.appendChild(item);
|
||||||
|
},
|
||||||
|
|
||||||
// Update hover tooltip for seeking
|
// Update hover tooltip for seeking
|
||||||
updateSeekTooltip(event) {
|
updateSeekTooltip(event) {
|
||||||
// Bail if setting not true
|
// Bail if setting not true
|
||||||
@ -353,7 +390,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display the time a click would seek to
|
// Display the time a click would seek to
|
||||||
ui.updateTimeDisplay.call(this, this.duration / 100 * percent, this.elements.display.seekTooltip);
|
ui.updateTimeDisplay.call(this, this.elements.display.seekTooltip, this.duration / 100 * percent);
|
||||||
|
|
||||||
// Set position
|
// Set position
|
||||||
this.elements.display.seekTooltip.style.left = `${percent}%`;
|
this.elements.display.seekTooltip.style.left = `${percent}%`;
|
||||||
@ -390,6 +427,7 @@ const controls = {
|
|||||||
// Set the YouTube quality menu
|
// Set the YouTube quality menu
|
||||||
// TODO: Support for HTML5
|
// TODO: Support for HTML5
|
||||||
setQualityMenu(options) {
|
setQualityMenu(options) {
|
||||||
|
const type = 'quality';
|
||||||
const list = this.elements.settings.panes.quality.querySelector('ul');
|
const list = this.elements.settings.panes.quality.querySelector('ul');
|
||||||
|
|
||||||
// Set options if passed and filter based on config
|
// Set options if passed and filter based on config
|
||||||
@ -401,7 +439,7 @@ const controls = {
|
|||||||
|
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !utils.is.empty(this.options.quality) && this.type === 'youtube';
|
const toggle = !utils.is.empty(this.options.quality) && this.type === 'youtube';
|
||||||
controls.toggleTab.call(this, 'quality', toggle);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
// If we're hiding, nothing more to do
|
// If we're hiding, nothing more to do
|
||||||
if (!toggle) {
|
if (!toggle) {
|
||||||
@ -443,35 +481,18 @@ const controls = {
|
|||||||
return controls.createBadge.call(this, label);
|
return controls.createBadge.call(this, label);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.options.quality.forEach(quality => {
|
this.options.quality.forEach(quality =>
|
||||||
const item = utils.createElement('li');
|
controls.createMenuItem.call(
|
||||||
|
this,
|
||||||
|
quality,
|
||||||
|
list,
|
||||||
|
type,
|
||||||
|
controls.getLabel.call(this, 'quality', quality),
|
||||||
|
getBadge(quality)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const label = utils.createElement('label', {
|
controls.updateSetting.call(this, type, list);
|
||||||
class: this.config.classNames.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const radio = utils.createElement(
|
|
||||||
'input',
|
|
||||||
utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs.quality), {
|
|
||||||
type: 'radio',
|
|
||||||
name: 'plyr-quality',
|
|
||||||
value: quality,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
label.appendChild(radio);
|
|
||||||
label.appendChild(document.createTextNode(controls.getLabel.call(this, 'quality', quality)));
|
|
||||||
|
|
||||||
const badge = getBadge(quality);
|
|
||||||
if (utils.is.htmlElement(badge)) {
|
|
||||||
label.appendChild(badge);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.appendChild(label);
|
|
||||||
list.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
controls.updateSetting.call(this, 'quality', list);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Translate a value into a nice label
|
// Translate a value into a nice label
|
||||||
@ -573,7 +594,7 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Set the looping options
|
// Set the looping options
|
||||||
setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panes.loop.querySelector('ul');
|
const list = this.elements.settings.panes.loop.querySelector('ul');
|
||||||
|
|
||||||
@ -609,7 +630,7 @@ const controls = {
|
|||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
},
|
}, */
|
||||||
|
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
@ -631,11 +652,13 @@ const controls = {
|
|||||||
|
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
setCaptionsMenu() {
|
setCaptionsMenu() {
|
||||||
|
// TODO: Captions or language? Currently it's mixed
|
||||||
|
const type = 'captions';
|
||||||
const list = this.elements.settings.panes.captions.querySelector('ul');
|
const list = this.elements.settings.panes.captions.querySelector('ul');
|
||||||
|
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !utils.is.empty(this.captions.tracks);
|
const toggle = !utils.is.empty(this.captions.tracks);
|
||||||
controls.toggleTab.call(this, 'captions', toggle);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
utils.emptyElement(list);
|
utils.emptyElement(list);
|
||||||
@ -648,7 +671,6 @@ const controls = {
|
|||||||
// Re-map the tracks into just the data we need
|
// Re-map the tracks into just the data we need
|
||||||
const tracks = Array.from(this.captions.tracks).map(track => ({
|
const tracks = Array.from(this.captions.tracks).map(track => ({
|
||||||
language: track.language,
|
language: track.language,
|
||||||
badge: true,
|
|
||||||
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -660,41 +682,24 @@ const controls = {
|
|||||||
|
|
||||||
// Generate options
|
// Generate options
|
||||||
tracks.forEach(track => {
|
tracks.forEach(track => {
|
||||||
const item = utils.createElement('li');
|
controls.createMenuItem.call(
|
||||||
|
this,
|
||||||
const label = utils.createElement('label', {
|
track.language,
|
||||||
class: this.config.classNames.control,
|
list,
|
||||||
});
|
'language',
|
||||||
|
track.label || track.language,
|
||||||
const radio = utils.createElement(
|
controls.createBadge.call(this, track.language.toUpperCase()),
|
||||||
'input',
|
track.language.toLowerCase() === this.captions.language.toLowerCase()
|
||||||
utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs.language), {
|
|
||||||
type: 'radio',
|
|
||||||
name: 'plyr-language',
|
|
||||||
value: track.language,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (track.language.toLowerCase() === this.captions.language.toLowerCase()) {
|
|
||||||
radio.checked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
label.appendChild(radio);
|
|
||||||
label.appendChild(document.createTextNode(track.label || track.language));
|
|
||||||
|
|
||||||
if (track.badge) {
|
|
||||||
label.appendChild(controls.createBadge.call(this, track.language.toUpperCase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
item.appendChild(label);
|
|
||||||
list.appendChild(item);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
controls.updateSetting.call(this, 'captions', list);
|
controls.updateSetting.call(this, type, list);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
setSpeedMenu(options) {
|
setSpeedMenu(options) {
|
||||||
|
const type = 'speed';
|
||||||
|
|
||||||
// Set options if passed and filter based on config
|
// Set options if passed and filter based on config
|
||||||
if (utils.is.array(options)) {
|
if (utils.is.array(options)) {
|
||||||
this.options.speed = options.filter(speed => this.config.speed.options.includes(speed));
|
this.options.speed = options.filter(speed => this.config.speed.options.includes(speed));
|
||||||
@ -704,7 +709,7 @@ const controls = {
|
|||||||
|
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !utils.is.empty(this.options.speed);
|
const toggle = !utils.is.empty(this.options.speed);
|
||||||
controls.toggleTab.call(this, 'speed', toggle);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
// If we're hiding, nothing more to do
|
// If we're hiding, nothing more to do
|
||||||
if (!toggle) {
|
if (!toggle) {
|
||||||
@ -722,39 +727,23 @@ const controls = {
|
|||||||
utils.emptyElement(list);
|
utils.emptyElement(list);
|
||||||
|
|
||||||
// Create items
|
// Create items
|
||||||
this.options.speed.forEach(speed => {
|
this.options.speed.forEach(speed =>
|
||||||
const item = utils.createElement('li');
|
controls.createMenuItem.call(this, speed, list, type, controls.getLabel.call(this, 'speed', speed))
|
||||||
|
);
|
||||||
|
|
||||||
const label = utils.createElement('label', {
|
controls.updateSetting.call(this, type, list);
|
||||||
class: this.config.classNames.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const radio = utils.createElement(
|
|
||||||
'input',
|
|
||||||
utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs.speed), {
|
|
||||||
type: 'radio',
|
|
||||||
name: 'plyr-speed',
|
|
||||||
value: speed,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
label.appendChild(radio);
|
|
||||||
label.insertAdjacentHTML('beforeend', controls.getLabel.call(this, 'speed', speed));
|
|
||||||
item.appendChild(label);
|
|
||||||
list.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
controls.updateSetting.call(this, 'speed', list);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Show/hide menu
|
// Show/hide menu
|
||||||
toggleMenu(event) {
|
toggleMenu(event) {
|
||||||
const { form } = this.elements.settings;
|
const { form } = this.elements.settings;
|
||||||
const button = this.elements.buttons.settings;
|
const button = this.elements.buttons.settings;
|
||||||
const show = utils.is.boolean(event) ? event : form && form.getAttribute('aria-hidden') === 'true';
|
const show = utils.is.boolean(event)
|
||||||
|
? event
|
||||||
|
: utils.is.htmlElement(form) && form.getAttribute('aria-hidden') === 'true';
|
||||||
|
|
||||||
if (utils.is.event(event)) {
|
if (utils.is.event(event)) {
|
||||||
const isMenuItem = form && form.contains(event.target);
|
const isMenuItem = utils.is.htmlElement(form) && form.contains(event.target);
|
||||||
const isButton = event.target === this.elements.buttons.settings;
|
const isButton = event.target === this.elements.buttons.settings;
|
||||||
|
|
||||||
// If the click was inside the form or if the click
|
// If the click was inside the form or if the click
|
||||||
@ -771,10 +760,11 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set form and button attributes
|
// Set form and button attributes
|
||||||
if (button) {
|
if (utils.is.htmlElement(button)) {
|
||||||
button.setAttribute('aria-expanded', show);
|
button.setAttribute('aria-expanded', show);
|
||||||
}
|
}
|
||||||
if (form) {
|
|
||||||
|
if (utils.is.htmlElement(form)) {
|
||||||
form.setAttribute('aria-hidden', !show);
|
form.setAttribute('aria-hidden', !show);
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
@ -882,6 +872,9 @@ const controls = {
|
|||||||
pane.setAttribute('aria-hidden', !show);
|
pane.setAttribute('aria-hidden', !show);
|
||||||
tab.setAttribute('aria-expanded', show);
|
tab.setAttribute('aria-expanded', show);
|
||||||
pane.removeAttribute('tabindex');
|
pane.removeAttribute('tabindex');
|
||||||
|
|
||||||
|
// Focus the first item
|
||||||
|
pane.querySelectorAll('button:not(:disabled), input:not(:disabled), [tabindex]')[0].focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Build the default HTML
|
// Build the default HTML
|
||||||
|
@ -22,13 +22,20 @@ const defaults = {
|
|||||||
// Pass a custom duration
|
// Pass a custom duration
|
||||||
duration: null,
|
duration: null,
|
||||||
|
|
||||||
// Display the media duration
|
// Display the media duration on load in the current time position
|
||||||
|
// If you have opted to display both duration and currentTime, this is ignored
|
||||||
displayDuration: true,
|
displayDuration: true,
|
||||||
|
|
||||||
|
// Invert the current time to be a countdown
|
||||||
|
invertTime: true,
|
||||||
|
|
||||||
|
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
||||||
|
toggleInvert: true,
|
||||||
|
|
||||||
// Aspect ratio (for embeds)
|
// Aspect ratio (for embeds)
|
||||||
ratio: '16:9',
|
ratio: '16:9',
|
||||||
|
|
||||||
// Click video to play
|
// Click video container to play/pause
|
||||||
clickToPlay: true,
|
clickToPlay: true,
|
||||||
|
|
||||||
// Auto hide the controls
|
// Auto hide the controls
|
||||||
@ -276,6 +283,7 @@ const defaults = {
|
|||||||
isIos: 'plyr--is-ios',
|
isIos: 'plyr--is-ios',
|
||||||
isTouch: 'plyr--is-touch',
|
isTouch: 'plyr--is-touch',
|
||||||
uiSupported: 'plyr--full-ui',
|
uiSupported: 'plyr--full-ui',
|
||||||
|
noTransition: 'plyr--no-transition',
|
||||||
menu: {
|
menu: {
|
||||||
value: 'plyr__menu__value',
|
value: 'plyr__menu__value',
|
||||||
badge: 'plyr__badge',
|
badge: 'plyr__badge',
|
||||||
@ -298,6 +306,11 @@ const defaults = {
|
|||||||
},
|
},
|
||||||
tabFocus: 'plyr__tab-focus',
|
tabFocus: 'plyr__tab-focus',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// API keys
|
||||||
|
keys: {
|
||||||
|
google: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defaults;
|
export default defaults;
|
||||||
|
@ -209,7 +209,7 @@ const listeners = {
|
|||||||
// Toggle controls on mouse events and entering fullscreen
|
// Toggle controls on mouse events and entering fullscreen
|
||||||
utils.on(
|
utils.on(
|
||||||
this.elements.container,
|
this.elements.container,
|
||||||
'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen',
|
'click mouseenter mouseleave mousemove touchmove enterfullscreen exitfullscreen',
|
||||||
event => {
|
event => {
|
||||||
this.toggleControls(event);
|
this.toggleControls(event);
|
||||||
}
|
}
|
||||||
@ -217,11 +217,11 @@ const listeners = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle user exiting fullscreen by escaping etc
|
// Handle user exiting fullscreen by escaping etc
|
||||||
if (fullscreen.enabled) {
|
/* if (fullscreen.enabled) {
|
||||||
utils.on(document, fullscreen.eventType, event => {
|
utils.on(document, fullscreen.eventType, event => {
|
||||||
this.toggleFullscreen(event);
|
this.toggleFullscreen(event);
|
||||||
});
|
});
|
||||||
}
|
} */
|
||||||
},
|
},
|
||||||
|
|
||||||
// Listen for media events
|
// Listen for media events
|
||||||
@ -230,7 +230,7 @@ const listeners = {
|
|||||||
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
|
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
|
||||||
|
|
||||||
// Display duration
|
// Display duration
|
||||||
utils.on(this.media, 'durationchange loadedmetadata', event => ui.displayDuration.call(this, event));
|
utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event));
|
||||||
|
|
||||||
// Handle the media finishing
|
// Handle the media finishing
|
||||||
utils.on(this.media, 'ended', () => {
|
utils.on(this.media, 'ended', () => {
|
||||||
@ -463,11 +463,16 @@ const listeners = {
|
|||||||
controls.showTab.call(this, event);
|
controls.showTab.call(this, event);
|
||||||
|
|
||||||
// Settings menu items - use event delegation as items are added/removed
|
// Settings menu items - use event delegation as items are added/removed
|
||||||
// Settings - Language
|
|
||||||
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
|
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
|
||||||
|
// Settings - Language
|
||||||
proxy(event, 'language', () => {
|
proxy(event, 'language', () => {
|
||||||
this.toggleCaptions(true);
|
const language = event.target.value;
|
||||||
this.language = event.target.value.toLowerCase();
|
|
||||||
|
this.toggleCaptions(!utils.is.empty(language));
|
||||||
|
|
||||||
|
if (!utils.is.empty(language)) {
|
||||||
|
this.language = event.target.value.toLowerCase();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
|
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
|
||||||
// Settings - Quality
|
// Settings - Quality
|
||||||
@ -479,7 +484,7 @@ const listeners = {
|
|||||||
proxy(event, 'speed', () => {
|
proxy(event, 'speed', () => {
|
||||||
this.speed = parseFloat(event.target.value);
|
this.speed = parseFloat(event.target.value);
|
||||||
});
|
});
|
||||||
} else if (utils.matches(event.target, this.config.selectors.buttons.loop)) {
|
} /* else if (utils.matches(event.target, this.config.selectors.buttons.loop)) {
|
||||||
// Settings - Looping
|
// Settings - Looping
|
||||||
// TODO: use toggle buttons
|
// TODO: use toggle buttons
|
||||||
proxy(event, 'loop', () => {
|
proxy(event, 'loop', () => {
|
||||||
@ -488,7 +493,7 @@ const listeners = {
|
|||||||
|
|
||||||
this.console.warn('Set loop');
|
this.console.warn('Set loop');
|
||||||
});
|
});
|
||||||
}
|
} */
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
@ -498,6 +503,20 @@ const listeners = {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Current time invert
|
||||||
|
// Only if one time element is used for both currentTime and duration
|
||||||
|
if (this.config.toggleInvert && !utils.is.htmlElement(this.elements.display.duration)) {
|
||||||
|
utils.on(this.elements.display.currentTime, 'click', () => {
|
||||||
|
// Do nothing if we're at the start
|
||||||
|
if (this.currentTime === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.invertTime = !this.config.invertTime;
|
||||||
|
ui.timeUpdate.call(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
utils.on(this.elements.inputs.volume, inputEvent, event =>
|
utils.on(this.elements.inputs.volume, inputEvent, event =>
|
||||||
proxy(event, 'volume', () => {
|
proxy(event, 'volume', () => {
|
||||||
@ -533,7 +552,7 @@ const listeners = {
|
|||||||
// TODO: Check we need capture here
|
// TODO: Check we need capture here
|
||||||
utils.on(
|
utils.on(
|
||||||
this.elements.controls,
|
this.elements.controls,
|
||||||
'focus blur',
|
'focusin focusout',
|
||||||
event => {
|
event => {
|
||||||
this.toggleControls(event);
|
this.toggleControls(event);
|
||||||
},
|
},
|
||||||
|
@ -23,19 +23,21 @@ const youtube = {
|
|||||||
// Set ID
|
// Set ID
|
||||||
this.media.setAttribute('id', utils.generateId(this.type));
|
this.media.setAttribute('id', utils.generateId(this.type));
|
||||||
|
|
||||||
// Get the title
|
// Get the media title via Google API
|
||||||
const key = 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c';
|
const key = this.config.keys.google;
|
||||||
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&fields=items(snippet(title))&part=snippet&key=${key}`;
|
if (utils.is.string(key) && !utils.is.empty(key)) {
|
||||||
|
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
|
||||||
|
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(response => response.json())
|
.then(response => (response.ok ? response.json() : null))
|
||||||
.then(obj => {
|
.then(result => {
|
||||||
if (utils.is.object(obj)) {
|
if (result !== null && utils.is.object(result)) {
|
||||||
this.config.title = obj.items[0].snippet.title;
|
this.config.title = result.items[0].snippet.title;
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
// Setup API
|
// Setup API
|
||||||
if (utils.is.object(window.YT)) {
|
if (utils.is.object(window.YT)) {
|
||||||
|
@ -669,7 +669,7 @@ class Plyr {
|
|||||||
const language = input.toLowerCase();
|
const language = input.toLowerCase();
|
||||||
|
|
||||||
// If nothing to change, bail
|
// If nothing to change, bail
|
||||||
if (this.captions.language === language) {
|
if (this.language === language) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,31 +797,28 @@ class Plyr {
|
|||||||
|
|
||||||
// Show the player controls in fullscreen mode
|
// Show the player controls in fullscreen mode
|
||||||
toggleControls(toggle) {
|
toggleControls(toggle) {
|
||||||
const player = this;
|
|
||||||
|
|
||||||
// We need controls of course...
|
// We need controls of course...
|
||||||
if (!utils.is.htmlElement(this.elements.controls)) {
|
if (!utils.is.htmlElement(this.elements.controls)) {
|
||||||
return player;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't hide if config says not to, it's audio, or not ready or loading
|
// Don't hide if config says not to, it's audio, or not ready or loading
|
||||||
if (!this.supported.ui || !this.config.hideControls || this.type === 'audio') {
|
if (!this.supported.ui || !this.config.hideControls || this.type === 'audio') {
|
||||||
return player;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
let delay = 0;
|
let delay = 0;
|
||||||
let show = toggle;
|
let show = toggle;
|
||||||
let isEnterFullscreen = false;
|
let isEnterFullscreen = false;
|
||||||
const loading = utils.hasClass(this.elements.container, this.config.classNames.loading);
|
|
||||||
|
|
||||||
// Default to false if no boolean
|
// Get toggle state if not set
|
||||||
if (!utils.is.boolean(toggle)) {
|
if (!utils.is.boolean(toggle)) {
|
||||||
if (utils.is.event(toggle)) {
|
if (utils.is.event(toggle)) {
|
||||||
// Is the enter fullscreen event
|
// Is the enter fullscreen event
|
||||||
isEnterFullscreen = toggle.type === 'enterfullscreen';
|
isEnterFullscreen = toggle.type === 'enterfullscreen';
|
||||||
|
|
||||||
// Whether to show controls
|
// Whether to show controls
|
||||||
show = ['mousemove', 'touchstart', 'mouseenter', 'focus'].includes(toggle.type);
|
show = ['click', 'mousemove', 'touchmove', 'mouseenter', 'focusin'].includes(toggle.type);
|
||||||
|
|
||||||
// Delay hiding on move events
|
// Delay hiding on move events
|
||||||
if (['mousemove', 'touchmove'].includes(toggle.type)) {
|
if (['mousemove', 'touchmove'].includes(toggle.type)) {
|
||||||
@ -829,8 +826,9 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delay a little more for keyboard users
|
// Delay a little more for keyboard users
|
||||||
if (toggle.type === 'focus') {
|
if (toggle.type === 'focusin') {
|
||||||
delay = 3000;
|
delay = 3000;
|
||||||
|
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
show = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
|
show = utils.hasClass(this.elements.container, this.config.classNames.hideControls);
|
||||||
@ -841,7 +839,7 @@ class Plyr {
|
|||||||
window.clearTimeout(this.timers.hover);
|
window.clearTimeout(this.timers.hover);
|
||||||
|
|
||||||
// If the mouse is not over the controls, set a timeout to hide them
|
// If the mouse is not over the controls, set a timeout to hide them
|
||||||
if (show || this.media.paused || loading) {
|
if (show || this.media.paused || this.loading) {
|
||||||
// Check if controls toggled
|
// Check if controls toggled
|
||||||
const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false);
|
const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false);
|
||||||
|
|
||||||
@ -851,8 +849,8 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Always show controls when paused or if touch
|
// Always show controls when paused or if touch
|
||||||
if (this.media.paused || loading) {
|
if (this.media.paused || this.loading) {
|
||||||
return player;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay for hiding on touch
|
// Delay for hiding on touch
|
||||||
@ -870,6 +868,11 @@ class Plyr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore transition behaviour
|
||||||
|
if (!utils.hasClass(this.elements.container, this.config.classNames.hideControls)) {
|
||||||
|
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if controls toggled
|
// Check if controls toggled
|
||||||
const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, true);
|
const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, true);
|
||||||
|
|
||||||
|
139
src/js/ui.js
139
src/js/ui.js
@ -15,7 +15,7 @@ const ui = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Toggle native HTML5 media controls
|
// Toggle native HTML5 media controls
|
||||||
toggleNativeControls(toggle) {
|
toggleNativeControls(toggle = false) {
|
||||||
if (toggle && this.isHTML5) {
|
if (toggle && this.isHTML5) {
|
||||||
this.media.setAttribute('controls', '');
|
this.media.setAttribute('controls', '');
|
||||||
} else {
|
} else {
|
||||||
@ -100,26 +100,6 @@ const ui = {
|
|||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Show the duration on metadataloaded
|
|
||||||
displayDuration() {
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's only one time display, display duration there
|
|
||||||
if (!this.elements.display.duration && this.config.displayDuration && this.paused) {
|
|
||||||
ui.updateTimeDisplay.call(this, this.duration, this.elements.display.currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's a duration element, update content
|
|
||||||
if (this.elements.display.duration) {
|
|
||||||
ui.updateTimeDisplay.call(this, this.duration, this.elements.display.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tooltip (if visible)
|
|
||||||
controls.updateSeekTooltip.call(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Setup aria attribute for play and iframe title
|
// Setup aria attribute for play and iframe title
|
||||||
setTitle() {
|
setTitle() {
|
||||||
// Find the current text
|
// Find the current text
|
||||||
@ -165,23 +145,6 @@ const ui = {
|
|||||||
this.toggleControls(this.paused);
|
this.toggleControls(this.paused);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update volume UI and storage
|
|
||||||
updateVolume() {
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update range
|
|
||||||
if (utils.is.htmlElement(this.elements.inputs.volume)) {
|
|
||||||
ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update checkbox for mute state
|
|
||||||
if (utils.is.htmlElement(this.elements.buttons.mute)) {
|
|
||||||
utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Check if media is loading
|
// Check if media is loading
|
||||||
checkLoading(event) {
|
checkLoading(event) {
|
||||||
this.loading = event.type === 'waiting';
|
this.loading = event.type === 'waiting';
|
||||||
@ -199,8 +162,25 @@ const ui = {
|
|||||||
}, this.loading ? 250 : 0);
|
}, this.loading ? 250 : 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Update volume UI and storage
|
||||||
|
updateVolume() {
|
||||||
|
if (!this.supported.ui) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update range
|
||||||
|
if (utils.is.htmlElement(this.elements.inputs.volume)) {
|
||||||
|
ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update checkbox for mute state
|
||||||
|
if (utils.is.htmlElement(this.elements.buttons.mute)) {
|
||||||
|
utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Update seek value and lower fill
|
// Update seek value and lower fill
|
||||||
setRange(target, value) {
|
setRange(target, value = 0) {
|
||||||
if (!utils.is.htmlElement(target)) {
|
if (!utils.is.htmlElement(target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -214,9 +194,8 @@ const ui = {
|
|||||||
|
|
||||||
// Set <progress> value
|
// Set <progress> value
|
||||||
setProgress(target, input) {
|
setProgress(target, input) {
|
||||||
// Default to 0
|
const value = utils.is.number(input) ? input : 0;
|
||||||
const value = !utils.is.undefined(input) ? input : 0;
|
const progress = utils.is.htmlElement(target) ? target : this.elements.display.buffer;
|
||||||
const progress = !utils.is.undefined(target) ? target : this.elements.display.buffer;
|
|
||||||
|
|
||||||
// Update value and label
|
// Update value and label
|
||||||
if (utils.is.htmlElement(progress)) {
|
if (utils.is.htmlElement(progress)) {
|
||||||
@ -232,7 +211,7 @@ const ui = {
|
|||||||
|
|
||||||
// Update <progress> elements
|
// Update <progress> elements
|
||||||
updateProgress(event) {
|
updateProgress(event) {
|
||||||
if (!this.supported.ui) {
|
if (!this.supported.ui || !utils.is.event(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,41 +259,49 @@ const ui = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Update the displayed time
|
// Update the displayed time
|
||||||
updateTimeDisplay(value, element) {
|
updateTimeDisplay(target = null, time = 0, inverted = false) {
|
||||||
// Bail if there's no duration display
|
// Bail if there's no element to display or the value isn't a number
|
||||||
if (!utils.is.htmlElement(element)) {
|
if (!utils.is.htmlElement(target) || !utils.is.number(time)) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to 0
|
// Format time component to add leading zero
|
||||||
const time = !Number.isNaN(value) ? value : 0;
|
const format = value => `0${value}`.slice(-2);
|
||||||
|
|
||||||
let secs = parseInt(time % 60, 10);
|
// Helpers
|
||||||
let mins = parseInt((time / 60) % 60, 10);
|
const getHours = value => parseInt((value / 60 / 60) % 60, 10);
|
||||||
const hours = parseInt((time / 60 / 60) % 60, 10);
|
const getMinutes = value => parseInt((value / 60) % 60, 10);
|
||||||
|
const getSeconds = value => parseInt(value % 60, 10);
|
||||||
|
|
||||||
|
// Breakdown to hours, mins, secs
|
||||||
|
let hours = getHours(time);
|
||||||
|
const mins = getMinutes(time);
|
||||||
|
const secs = getSeconds(time);
|
||||||
|
|
||||||
// Do we need to display hours?
|
// Do we need to display hours?
|
||||||
const displayHours = parseInt((this.duration / 60 / 60) % 60, 10) > 0;
|
if (getHours(this.duration) > 0) {
|
||||||
|
hours = `${hours}:`;
|
||||||
// Ensure it's two digits. For example, 03 rather than 3.
|
} else {
|
||||||
secs = `0${secs}`.slice(-2);
|
hours = '';
|
||||||
mins = `0${mins}`.slice(-2);
|
}
|
||||||
|
|
||||||
// Generate display
|
|
||||||
const display = `${(displayHours ? `${hours}:` : '') + mins}:${secs}`;
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.textContent = display;
|
target.textContent = `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||||
|
|
||||||
// Return for looping
|
|
||||||
return display;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handle time change event
|
// Handle time change event
|
||||||
timeUpdate(event) {
|
timeUpdate(event) {
|
||||||
|
// Only invert if only one time element is displayed and used for both duration and currentTime
|
||||||
|
const invert = !utils.is.htmlElement(this.elements.display.duration) && this.config.invertTime;
|
||||||
|
|
||||||
// Duration
|
// Duration
|
||||||
ui.updateTimeDisplay.call(this, this.currentTime, this.elements.display.currentTime);
|
ui.updateTimeDisplay.call(
|
||||||
|
this,
|
||||||
|
this.elements.display.currentTime,
|
||||||
|
invert ? this.duration - this.currentTime : this.currentTime,
|
||||||
|
invert
|
||||||
|
);
|
||||||
|
|
||||||
// Ignore updates while seeking
|
// Ignore updates while seeking
|
||||||
if (event && event.type === 'timeupdate' && this.media.seeking) {
|
if (event && event.type === 'timeupdate' && this.media.seeking) {
|
||||||
@ -324,6 +311,26 @@ const ui = {
|
|||||||
// Playing progress
|
// Playing progress
|
||||||
ui.updateProgress.call(this, event);
|
ui.updateProgress.call(this, event);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Show the duration on metadataloaded
|
||||||
|
durationUpdate() {
|
||||||
|
if (!this.supported.ui) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's only one time display, display duration there
|
||||||
|
if (!utils.is.htmlElement(this.elements.display.duration) && this.config.displayDuration && this.paused) {
|
||||||
|
ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a duration element, update content
|
||||||
|
if (utils.is.htmlElement(this.elements.display.duration)) {
|
||||||
|
ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the tooltip (if visible)
|
||||||
|
controls.updateSeekTooltip.call(this);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ui;
|
export default ui;
|
||||||
|
@ -31,6 +31,9 @@ const utils = {
|
|||||||
htmlElement(input) {
|
htmlElement(input) {
|
||||||
return !this.undefined(input) && input instanceof HTMLElement;
|
return !this.undefined(input) && input instanceof HTMLElement;
|
||||||
},
|
},
|
||||||
|
textNode(input) {
|
||||||
|
return this.getConstructor(input) === Text;
|
||||||
|
},
|
||||||
event(input) {
|
event(input) {
|
||||||
return !this.undefined(input) && input instanceof Event;
|
return !this.undefined(input) && input instanceof Event;
|
||||||
},
|
},
|
||||||
@ -49,8 +52,8 @@ const utils = {
|
|||||||
return (
|
return (
|
||||||
input === null ||
|
input === null ||
|
||||||
typeof input === 'undefined' ||
|
typeof input === 'undefined' ||
|
||||||
((this.string(input) || this.array(input) || this.nodeList(input)) && input.length === 0) ||
|
((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) ||
|
||||||
(this.object(input) && Object.keys(input).length === 0)
|
(this.object(input) && !Object.keys(input).length)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getConstructor(input) {
|
getConstructor(input) {
|
||||||
@ -140,8 +143,12 @@ const utils = {
|
|||||||
|
|
||||||
// Get the sprite
|
// Get the sprite
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(response => response.text())
|
.then(response => (response.ok ? response.text() : null))
|
||||||
.then(text => {
|
.then(text => {
|
||||||
|
if (text === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (support.storage) {
|
if (support.storage) {
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
prefix + id,
|
prefix + id,
|
||||||
@ -152,7 +159,8 @@ const utils = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSprite.call(container, text);
|
updateSprite.call(container, text);
|
||||||
});
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -201,22 +209,6 @@ const utils = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Remove an element
|
|
||||||
removeElement(element) {
|
|
||||||
if (!utils.is.htmlElement(element) || !utils.is.htmlElement(element.parentNode)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
element.parentNode.removeChild(element);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Inaert an element after another
|
|
||||||
insertAfter(element, target) {
|
|
||||||
target.parentNode.insertBefore(element, target.nextSibling);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create a DocumentFragment
|
// Create a DocumentFragment
|
||||||
createElement(type, attributes, text) {
|
createElement(type, attributes, text) {
|
||||||
// Create a new <element>
|
// Create a new <element>
|
||||||
@ -236,12 +228,28 @@ const utils = {
|
|||||||
return element;
|
return element;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Inaert an element after another
|
||||||
|
insertAfter(element, target) {
|
||||||
|
target.parentNode.insertBefore(element, target.nextSibling);
|
||||||
|
},
|
||||||
|
|
||||||
// Insert a DocumentFragment
|
// Insert a DocumentFragment
|
||||||
insertElement(type, parent, attributes, text) {
|
insertElement(type, parent, attributes, text) {
|
||||||
// Inject the new <element>
|
// Inject the new <element>
|
||||||
parent.appendChild(utils.createElement(type, attributes, text));
|
parent.appendChild(utils.createElement(type, attributes, text));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Remove an element
|
||||||
|
removeElement(element) {
|
||||||
|
if (!utils.is.htmlElement(element) || !utils.is.htmlElement(element.parentNode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.parentNode.removeChild(element);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
},
|
||||||
|
|
||||||
// Remove all child elements
|
// Remove all child elements
|
||||||
emptyElement(element) {
|
emptyElement(element) {
|
||||||
let { length } = element.childNodes;
|
let { length } = element.childNodes;
|
||||||
@ -433,9 +441,9 @@ const utils = {
|
|||||||
|
|
||||||
// Trap focus inside container
|
// Trap focus inside container
|
||||||
trapFocus() {
|
trapFocus() {
|
||||||
const tabbables = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||||
const first = tabbables[0];
|
const first = focusable[0];
|
||||||
const last = tabbables[tabbables.length - 1];
|
const last = focusable[focusable.length - 1];
|
||||||
|
|
||||||
utils.on(
|
utils.on(
|
||||||
this.elements.container,
|
this.elements.container,
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
line-height: @plyr-line-height;
|
line-height: @plyr-line-height;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
.plyr-font-smoothing(off);
|
.plyr-font-smoothing(off);
|
||||||
|
|
||||||
// Media elements
|
// Media elements
|
||||||
@ -22,6 +23,11 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0 0 0 3px fade(#000, 10%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full UI only
|
// Full UI only
|
||||||
|
@ -26,4 +26,5 @@
|
|||||||
|
|
||||||
@import 'states/fullscreen';
|
@import 'states/fullscreen';
|
||||||
|
|
||||||
|
@import 'utils/animation';
|
||||||
@import 'utils/hidden';
|
@import 'utils/hidden';
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__badge {
|
.plyr__badge {
|
||||||
padding: 0 4px;
|
padding: 3px 4px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: @plyr-menu-color;
|
background: @plyr-menu-color;
|
||||||
color: @plyr-menu-bg;
|
color: @plyr-menu-bg;
|
||||||
font-size: @plyr-font-size-tiny;
|
font-size: @plyr-font-size-tiny;
|
||||||
line-height: 1.5;
|
line-height: 1;
|
||||||
.plyr-font-smoothing(on);
|
.plyr-font-smoothing(on);
|
||||||
}
|
}
|
||||||
|
@ -57,9 +57,9 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
padding: ceil(@plyr-control-spacing * 1.25);
|
padding: ceil(@plyr-control-spacing * 1.5);
|
||||||
background: fade(@plyr-video-control-bg-hover, 80%);
|
background: fade(@plyr-video-control-bg-hover, 80%);
|
||||||
border: 3px solid currentColor;
|
border: 0;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
box-shadow: 0 1px 1px fade(#000, 15%);
|
box-shadow: 0 1px 1px fade(#000, 15%);
|
||||||
color: @plyr-video-control-color;
|
color: @plyr-video-control-color;
|
||||||
@ -81,7 +81,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 1px dotted fade(@plyr-video-control-color, 50%);
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.plyr__tab-focus {
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: 0 0 0 3px fade(@plyr-video-control-color, 50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,11 +129,45 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
label.plyr__control {
|
label.plyr__control {
|
||||||
padding-left: ceil(@plyr-control-padding * 2.5);
|
padding-left: @plyr-control-padding;
|
||||||
|
|
||||||
input[type='radio'] {
|
/*input[type='radio'] {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -@plyr-control-padding;
|
left: -@plyr-control-padding;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
input[type='radio'] + span {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: fade(#000, 10%);
|
||||||
|
margin-right: @plyr-control-spacing;
|
||||||
|
box-shadow: inset 0 1px 1px fade(#000, 15%);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
top: 4px;
|
||||||
|
left: 4px;
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='radio']:checked + span {
|
||||||
|
background: @plyr-color-main;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
border-radius: (@plyr-range-thumb-height * 2);
|
||||||
|
|
||||||
// Used in JS to populate lower fill for WebKit
|
// Used in JS to populate lower fill for WebKit
|
||||||
color: @plyr-range-selected-bg;
|
color: @plyr-range-selected-bg;
|
||||||
@ -79,10 +81,6 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.plyr__tab-focus {
|
|
||||||
outline-offset: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pressed styles
|
// Pressed styles
|
||||||
&:active {
|
&:active {
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
@ -114,7 +112,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.plyr__tab-focus {
|
&.plyr__tab-focus {
|
||||||
outline: 1px dotted fade(@plyr-video-control-color, 50%);
|
box-shadow: 0 0 0 3px fade(@plyr-video-control-color, 50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +131,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.plyr__tab-focus {
|
&.plyr__tab-focus {
|
||||||
outline: 1px dotted fade(@plyr-audio-control-color, 50%);
|
box-shadow: 0 0 0 3px fade(@plyr-audio-control-color, 50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/less/utils/animation.less
Normal file
7
src/less/utils/animation.less
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// --------------------------------------------------------------
|
||||||
|
// Animation utils
|
||||||
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
|
.plyr--no-transition {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user