Captions fix

This commit is contained in:
Sam Potts 2017-11-19 17:54:38 +11:00
parent 3f744ef63a
commit 4b62a5c74d
14 changed files with 126 additions and 157 deletions

2
demo/dist/demo.js vendored
View File

@ -1,3 +1,3 @@
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")); 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:"media/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

File diff suppressed because one or more lines are too long

View File

@ -109,7 +109,7 @@ document.addEventListener('DOMContentLoaded', () => {
type: 'video/mp4', type: 'video/mp4',
}, },
], ],
poster: 'hmedia/View_From_A_Blue_Moon_Trailer-HD.jpg', poster: 'media/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [ tracks: [
{ {
kind: 'captions', kind: 'captions',

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -33,8 +33,6 @@ const captions = {
// Only Vimeo and HTML5 video supported at this point // Only Vimeo and HTML5 video supported at this point
if (!['video', 'vimeo'].includes(this.type) || (this.type === 'video' && !support.textTracks)) { if (!['video', 'vimeo'].includes(this.type) || (this.type === 'video' && !support.textTracks)) {
this.captions.tracks = null;
// Clear menu and hide // Clear menu and hide
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) { if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this); controls.setCaptionsMenu.call(this);
@ -49,117 +47,102 @@ const captions = {
'div', 'div',
utils.getAttributesFromSelector(this.config.selectors.captions) utils.getAttributesFromSelector(this.config.selectors.captions)
); );
utils.insertAfter(this.elements.captions, this.elements.wrapper);
}
// Get tracks from HTML5 utils.insertAfter(this.elements.captions, this.elements.wrapper);
if (this.type === 'video') {
this.captions.tracks = this.media.textTracks;
} }
// Set the class hook // Set the class hook
utils.toggleClass( utils.toggleClass(
this.elements.container, this.elements.container,
this.config.classNames.captions.enabled, this.config.classNames.captions.enabled,
!utils.is.empty(this.captions.tracks) !utils.is.empty(captions.getTracks.call(this))
); );
// If no caption file exists, hide container for caption text // If no caption file exists, hide container for caption text
if (utils.is.empty(this.captions.tracks)) { if (utils.is.empty(captions.getTracks.call(this))) {
return; return;
} }
// Set language
captions.setLanguage.call(this);
// Enable UI // Enable UI
captions.show.call(this); captions.show.call(this);
// Get a track
const setCurrentTrack = () => {
// Reset by default
this.captions.currentTrack = null;
// Filter doesn't seem to work for a TextTrackList :-(
Array.from(this.captions.tracks).forEach(track => {
if (track.language.toLowerCase() === this.language.toLowerCase()) {
this.captions.currentTrack = track;
console.warn(`Set current track to ${this.language}`);
}
});
};
// Get current track
setCurrentTrack();
// If we couldn't get the requested language, revert to default
if (!utils.is.track(this.captions.currentTrack)) {
const { language } = this.config.captions;
// Reset to default
// We don't update user storage as the selected language could become available
this.captions.language = language;
// Get fallback track
setCurrentTrack();
// If no match, disable captions
if (!utils.is.track(this.captions.currentTrack)) {
this.toggleCaptions(false);
}
controls.updateSetting.call(this, 'captions');
}
// Setup HTML5 track rendering
if (this.type === 'video') {
// Turn off native caption rendering to avoid double captions
Array.from(this.captions.tracks).forEach(track => {
// Remove previous bindings (if we've changed source or language)
utils.off(track, 'cuechange', event => captions.setCue.call(this, event));
// Hide captions
// eslint-disable-next-line
track.mode = 'hidden';
});
// Check if suported kind
const supported =
this.captions.currentTrack && ['captions', 'subtitles'].includes(this.captions.currentTrack.kind);
if (utils.is.track(this.captions.currentTrack) && supported) {
utils.on(this.captions.currentTrack, 'cuechange', event => captions.setCue.call(this, event));
// If we change the active track while a cue is already displayed we need to update it
if (this.captions.currentTrack.activeCues && this.captions.currentTrack.activeCues.length > 0) {
captions.setCue.call(this, this.captions.currentTrack);
}
}
} else if (this.type === 'vimeo' && this.captions.active) {
this.embed.enableTextTrack(this.captions.language);
}
// Set available languages in list // Set available languages in list
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) { if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this); controls.setCaptionsMenu.call(this);
} }
}, },
// Set the captions language
setLanguage() {
// Setup HTML5 track rendering
if (this.type === 'video') {
captions.getTracks.call(this).forEach(track => {
// Remove previous bindings
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
// Turn off native caption rendering to avoid double captions
// eslint-disable-next-line
track.mode = 'hidden';
});
// Get current track
const currentTrack = captions.getCurrentTrack.call(this);
// Check if suported kind
if (utils.is.track(currentTrack)) {
// If we change the active track while a cue is already displayed we need to update it
if (Array.from(currentTrack.activeCues || []).length) {
captions.setCue.call(this, currentTrack);
}
}
} else if (this.type === 'vimeo' && this.captions.active) {
this.embed.enableTextTrack(this.language);
}
},
// Get the tracks
getTracks() {
// Return empty array at least
if (utils.is.nullOrUndefined(this.media)) {
return [];
}
// Only get accepted kinds
return Array.from(this.media.textTracks || []).filter(track => ['captions', 'subtitles'].includes(track.kind));
},
// Get the current track for the current language
getCurrentTrack() {
return captions.getTracks.call(this).find(track => track.language.toLowerCase() === this.language);
},
// Display active caption if it contains text // Display active caption if it contains text
setCue(input) { setCue(input) {
// Get the track from the event if needed // Get the track from the event if needed
const track = utils.is.event(input) ? input.target : input; const track = utils.is.event(input) ? input.target : input;
const active = track.activeCues[0]; const active = track.activeCues[0];
const currentTrack = captions.getCurrentTrack.call(this);
// Only display current track
if (track !== currentTrack) {
return;
}
// Display a cue, if there is one // Display a cue, if there is one
if (utils.is.cue(active)) { if (utils.is.cue(active)) {
captions.set.call(this, active.getCueAsHTML()); captions.setText.call(this, active.getCueAsHTML());
} else { } else {
captions.set.call(this); captions.setText.call(this, null);
} }
utils.dispatchEvent.call(this, this.media, 'cuechange'); utils.dispatchEvent.call(this, this.media, 'cuechange');
}, },
// Set the current caption // Set the current caption
set(input) { setText(input) {
// Requires UI // Requires UI
if (!this.supported.ui) { if (!this.supported.ui) {
return; return;
@ -172,7 +155,7 @@ const captions = {
utils.emptyElement(this.elements.captions); utils.emptyElement(this.elements.captions);
// Default to empty // Default to empty
const caption = !utils.is.undefined(input) ? input : ''; const caption = !utils.is.nullOrUndefined(input) ? input : '';
// Set the span content // Set the span content
if (utils.is.string(caption)) { if (utils.is.string(caption)) {

17
src/js/controls.js vendored
View File

@ -5,6 +5,7 @@
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
import ui from './ui'; import ui from './ui';
import captions from './captions';
// Sniff out the browser // Sniff out the browser
const browser = utils.getBrowser(); const browser = utils.getBrowser();
@ -642,12 +643,16 @@ const controls = {
return null; return null;
} }
if (!support.textTracks || utils.is.empty(this.captions.tracks)) { if (!support.textTracks || !captions.getTracks.call(this).length) {
return this.config.i18n.none; return this.config.i18n.none;
} }
if (this.captions.enabled) { if (this.captions.enabled) {
return this.captions.currentTrack.label; const currentTrack = captions.getCurrentTrack.call(this);
if (utils.is.track(currentTrack)) {
return currentTrack.label;
}
} }
return this.config.i18n.disabled; return this.config.i18n.disabled;
@ -660,19 +665,19 @@ const controls = {
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 hasTracks = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, toggle); controls.toggleTab.call(this, type, hasTracks);
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// If there's no captions, bail // If there's no captions, bail
if (utils.is.empty(this.captions.tracks)) { if (!hasTracks) {
return; return;
} }
// 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 = captions.getTracks.call(this).map(track => ({
language: track.language, language: track.language,
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(), label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
})); }));

View File

@ -51,7 +51,7 @@ const fullscreen = {
return false; return false;
} }
const target = utils.is.undefined(element) ? document.body : element; const target = utils.is.nullOrUndefined(element) ? document.body : element;
switch (prefix) { switch (prefix) {
case '': case '':
@ -71,7 +71,7 @@ const fullscreen = {
return false; return false;
} }
const target = utils.is.undefined(element) ? document.body : element; const target = utils.is.nullOrUndefined(element) ? document.body : element;
return !prefix.length return !prefix.length
? target.requestFullScreen() ? target.requestFullScreen()

View File

@ -459,41 +459,24 @@ const listeners = {
// Settings menu // Settings menu
utils.on(this.elements.settings.form, 'click', event => { utils.on(this.elements.settings.form, 'click', event => {
// Show tab in menu event.stopPropagation();
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
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', () => {
const language = event.target.value; this.language = event.target.value;
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
proxy(event, 'quality', () => { proxy(event, 'quality', () => {
this.quality = event.target.value; this.quality = event.target.value;
}); });
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) { } else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
// Settings - Speed
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 {
// Settings - Looping controls.showTab.call(this, event);
// TODO: use toggle buttons }
proxy(event, 'loop', () => {
// TODO: This should be done in the method itself I think
// var value = event.target.getAttribute('data-loop__value') || event.target.getAttribute('data-loop__type');
this.console.warn('Set loop');
});
} */
}); });
// Seek // Seek

View File

@ -221,7 +221,7 @@ const vimeo = {
// Get captions // Get captions
player.embed.getTextTracks().then(tracks => { player.embed.getTextTracks().then(tracks => {
player.captions.tracks = tracks; player.media.textTracks = tracks;
captions.setup.call(player); captions.setup.call(player);
}); });
@ -232,7 +232,7 @@ const vimeo = {
cue = utils.stripHTML(data.cues[0].text); cue = utils.stripHTML(data.cues[0].text);
} }
captions.set.call(player, cue); captions.setText.call(player, cue);
}); });
player.embed.on('loaded', () => { player.embed.on('loaded', () => {

View File

@ -81,7 +81,6 @@ class Plyr {
// Captions // Captions
this.captions = { this.captions = {
enabled: null, enabled: null,
tracks: null,
currentTrack: null, currentTrack: null,
}; };
@ -116,7 +115,7 @@ class Plyr {
this.console.log('Support', support); this.console.log('Support', support);
// We need an element to setup // We need an element to setup
if (this.media === null || utils.is.undefined(this.media) || !utils.is.htmlElement(this.media)) { if (utils.is.nullOrUndefined(this.media) || !utils.is.htmlElement(this.media)) {
this.console.error('Setup failed: no suitable element passed'); this.console.error('Setup failed: no suitable element passed');
return; return;
} }
@ -665,6 +664,14 @@ class Plyr {
return; return;
} }
// Toggle captions based on input
this.toggleCaptions(!utils.is.empty(input));
// If empty string is passed, assume disable captions
if (utils.is.empty(input)) {
return;
}
// Normalize // Normalize
const language = input.toLowerCase(); const language = input.toLowerCase();
@ -673,20 +680,17 @@ class Plyr {
return; return;
} }
// Reset UI
this.toggleCaptions(true);
// Update config // Update config
this.captions.language = language; this.captions.language = language;
// Clear caption
captions.setText.call(this, null);
// Update captions
captions.setLanguage.call(this);
// Trigger an event // Trigger an event
utils.dispatchEvent.call(this, this.media, 'languagechange'); utils.dispatchEvent.call(this, this.media, 'languagechange');
// Clear caption
captions.set.call(this);
// Re-run setup
captions.setup.call(this);
} }
get language() { get language() {

View File

@ -23,49 +23,42 @@ const utils = {
return this.getConstructor(input) === Function; return this.getConstructor(input) === Function;
}, },
array(input) { array(input) {
return !this.undefined(input) && Array.isArray(input); return !this.nullOrUndefined(input) && Array.isArray(input);
}, },
nodeList(input) { nodeList(input) {
return !this.undefined(input) && input instanceof NodeList; return this.instanceof(input, window.NodeList);
}, },
htmlElement(input) { htmlElement(input) {
return !this.undefined(input) && input instanceof HTMLElement; return this.instanceof(input, window.HTMLElement);
}, },
textNode(input) { textNode(input) {
return this.getConstructor(input) === Text; return this.getConstructor(input) === Text;
}, },
event(input) { event(input) {
return !this.undefined(input) && input instanceof Event; return this.instanceof(input, window.Event);
}, },
cue(input) { cue(input) {
return this.instanceOf(input, window.TextTrackCue) || this.instanceOf(input, window.VTTCue); return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
}, },
track(input) { track(input) {
return ( return this.instanceof(input, window.TextTrack) || this.string(input.kind);
!this.undefined(input) && (this.instanceOf(input, window.TextTrack) || typeof input.kind === 'string')
);
}, },
undefined(input) { nullOrUndefined(input) {
return input !== null && typeof input === 'undefined'; return input === null || typeof input === 'undefined';
}, },
empty(input) { empty(input) {
return ( return (
input === null || this.nullOrUndefined(input) ||
typeof input === 'undefined' ||
((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) || ((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) ||
(this.object(input) && !Object.keys(input).length) (this.object(input) && !Object.keys(input).length)
); );
}, },
getConstructor(input) { instanceof(input, constructor) {
if (input === null || typeof input === 'undefined') {
return null;
}
return input.constructor;
},
instanceOf(input, constructor) {
return Boolean(input && constructor && input instanceof constructor); return Boolean(input && constructor && input instanceof constructor);
}, },
getConstructor(input) {
return !this.nullOrUndefined(input) ? input.constructor : null;
},
}, },
// Unfortunately, due to mixed support, UA sniffing is required // Unfortunately, due to mixed support, UA sniffing is required
@ -474,7 +467,7 @@ const utils = {
// Toggle event listener // Toggle event listener
toggleListener(elements, event, callback, toggle, passive, capture) { toggleListener(elements, event, callback, toggle, passive, capture) {
// Bail if no elements // Bail if no elements
if (elements === null || utils.is.undefined(elements)) { if (utils.is.nullOrUndefined(elements)) {
return; return;
} }

View File

@ -131,28 +131,24 @@
label.plyr__control { label.plyr__control {
padding-left: @plyr-control-padding; padding-left: @plyr-control-padding;
/*input[type='radio'] {
position: relative;
left: -@plyr-control-padding;
}*/
input[type='radio'] + span { input[type='radio'] + span {
position: relative; position: relative;
display: block; display: block;
height: 14px; flex-shrink: 0;
width: 14px; height: 16px;
width: 16px;
border-radius: 100%; border-radius: 100%;
background: fade(#000, 10%); background: fade(#000, 10%);
margin-right: @plyr-control-spacing; margin-right: @plyr-control-spacing;
box-shadow: inset 0 1px 1px fade(#000, 15%); transition: all 0.3s ease;
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
height: 6px; height: 6px;
width: 6px; width: 6px;
top: 4px; top: 5px;
left: 4px; left: 5px;
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;
background: #fff; background: #fff;
@ -169,6 +165,11 @@
opacity: 1; opacity: 1;
} }
} }
&.plyr__tab-focus input[type='radio'] + span,
&:hover input[type='radio'] + span {
background: fade(#000, 10%);
}
} }
// Option value // Option value