Commit 78c6a38c by Chris Rodriguez

LMS: new UI for video player + AFontGarde iconfonts

parent a8cca47c
// This is for A Font Garde
// It loads the icon font only when it's available.
// ---
// It is scoped to the video player for now, but we
// will eventually want to move this to the main font
// sheet, globally, so it applies to all use cases.
// --------
// Defaults: what displays if the icon font doesn't load.
// --------
// the html target is necessary for xblocks and xmodules, but works across the board
html:not('.afontgarde') .icon-fallback-img {
.fa-play {
background: url('#{$static-path}/images/fontawesome/play.svg') center center no-repeat;
}
.fa-pause {
background: url('#{$static-path}/images/fontawesome/pause.svg') center center no-repeat;
}
.fa-step-forward {
background: url('#{$static-path}/images/fontawesome/step-forward.svg') center center no-repeat;
}
.fa-arrows-alt {
background: url('#{$static-path}/images/fontawesome/arrows-alt.svg') center center no-repeat;
}
.fa-caret-right {
background: url('#{$static-path}/images/fontawesome/caret-right.svg') center center no-repeat;
}
.fa-caret-left {
background: url('#{$static-path}/images/fontawesome/caret-left.svg') center center no-repeat;
}
.fa-caret-up {
background: url('#{$static-path}/images/fontawesome/caret-up.svg') center center no-repeat;
}
.fa-compress {
background: url('#{$static-path}/images/fontawesome/compress.svg') center center no-repeat;
}
.fa-quote-left {
background: url('#{$static-path}/images/fontawesome/quote-left.svg') center center no-repeat;
}
.fa-volume-up {
background: url('#{$static-path}/images/fontawesome/volume-up.svg') center center no-repeat;
}
.fa-volume-down {
background: url('#{$static-path}/images/fontawesome/volume-down.svg') center center no-repeat;
}
.fa-volume-off {
background: url('#{$static-path}/images/fontawesome/volume-off.svg') center center no-repeat;
}
}
& {
margin-bottom: ($baseline*1.5);
}
......@@ -6,21 +69,25 @@
display: none;
}
div.video {
.video {
@include clearfix();
background: #f3f3f3;
background: rgb(240, 243, 245); // UXPL grayscale-cool xx-light;
display: block;
margin: 0 -12px;
padding: 12px;
border-radius: 5px;
outline: none;
&:focus, &:active, &:hover {
&:focus,
&:active,
&:hover {
border: 0;
}
&.is-initialized {
article.video-wrapper {
.video-wrapper {
.spinner {
display: none;
}
......@@ -29,12 +96,14 @@ div.video {
// CASE: video pre-roll state
&.is-pre-roll {
.slider {
visibility: hidden;
}
.video-player {
position: relative;
&:before {
display: block;
content: "";
......@@ -44,12 +113,12 @@ div.video {
}
}
div.tc-wrapper {
.tc-wrapper {
@include clearfix();
position: relative;
}
div.focus_grabber {
.focus_grabber {
position: relative;
display: inline;
width: 0px;
......@@ -60,7 +129,7 @@ div.video {
margin: 0;
padding: 0;
.video-download-button{
.video-download-button {
display: inline-block;
vertical-align: top;
margin: ($baseline*0.75) ($baseline/2) 0 0;
......@@ -75,16 +144,20 @@ div.video {
padding: ($baseline*0.75);
color: $lighter-base-font-color;
&:hover, &:focus {
&:hover,
&:focus {
background-color: $action-primary-active-bg;
color: $very-light-text;
}
}
}
.video-tracks {
> a {
border-radius: 3px 0 0 3px;
}
> a.external-track {
border-radius: 3px;
}
......@@ -116,16 +189,17 @@ div.video {
}
}
article.video-wrapper {
float: left;
margin-right: flex-gutter(9);
.video-wrapper {
@include float(left);
@include margin-right(flex-gutter(9));
width: flex-grid(6, 9);
background-color: black;
position: relative;
div.video-player-pre, div.video-player-post {
.video-player-pre,
.video-player-post {
height: 50px;
background-color: black;
background-color: rgb(17, 16, 16) // UXPL grayscale black;
}
.spinner {
......@@ -173,7 +247,7 @@ div.video {
}
}
section.video-player {
.video-player {
overflow: hidden;
min-height: 300px;
......@@ -185,7 +259,9 @@ div.video {
}
}
object, iframe, video {
object,
iframe,
video {
display: block;
border: none;
width: 100%;
......@@ -201,285 +277,272 @@ div.video {
}
}
section.video-controls {
.video-controls {
@include clearfix();
background: #333;
border: 1px solid $black;
border-top: 0;
color: $gray-l3;
position: relative;
border: 0;
background: rgb(40, 44, 46); // UXPL grayscale-cool x-dark
color: rgb(240, 243, 245); // UXPL grayscale-cool xx-light
&:hover, &:focus {
ul, div {
&:hover,
&:focus {
ul,
div {
opacity: 1;
}
}
%video-button {
@extend %ui-fake-link;
@include transition(none);
display: block;
font-weight: 700;
line-height: 46px;
%video-control {
@extend %t-strong;
@extend %t-title7;
display: inline-block;
vertical-align: middle;
margin: 0;
padding: 0 0 0 15px;
overflow: hidden;
text-indent: -9999px;
-webkit-font-smoothing: antialiased;
box-shadow: 1px 0 0 #555, inset 1px 0 0 #555;
color: $white;
border-width: 0 1px;
border-style: solid;
border-color: $black;
border: 0;
border-radius: 0;
padding: ($baseline / 2) ($baseline / 1.5);
background: rgb(40, 44, 46); // UXPL grayscale-cool x-dark
box-shadow: none;
text-shadow: none;
color: rgb(207, 216, 220); // UXPL grayscale-cool light
&:hover, &:focus {
background-color: #444;
color: $white;
text-decoration: none;
&:hover,
&:focus {
background: darken(rgb(40, 44, 46), 7%); // UXPL secondary
}
&:active,
&:focus {
color: $white;
background-color: #444;
text-decoration: none;
&.is-active,
&.active {
color: rgb(14, 166, 236); // UXPL primary accent
}
}
div.slider {
.control {
@extend %video-control;
.icon-fallback-img {
.icon {
// if the icon font doesn't render, we need to provide dimensions for the svg's
width: 1em;
height: 1em;
&.icon-hd {
// except when it's text, like with HD
// otherwise it's shifted to the right because "HD" is wider than 1em
width: auto;
}
}
}
}
.slider {
@include clearfix();
@include transform(scaleY(0.5) translate3d(0, 50%, 0));
background: #c2c2c2;
border: 1px solid $black;
border-radius: 0;
border-top: 1px solid $black;
box-shadow: inset 0 1px 0 #eee, 0 1px 0 #555;
@include transform-origin(bottom left);
@include transition(height .7s ease-in-out 0s);
position: absolute;
z-index: 1;
bottom: 100%;
left: 0;
right: 0;
height: 14px;
margin-left: -1px;
margin-right: -1px;
-webkit-transition: -webkit-transform 0.7s ease-in-out;
-moz-transition: -moz-transform 0.7s ease-in-out;
-ms-transition: -ms-transform 0.7s ease-in-out;
transition: transform 0.7s ease-in-out;
z-index: 1;
height: ($baseline / 4);
margin-left: 0;
border: 0;
border-radius: 0;
background: rgb(79, 89, 93); // UXPL grayscale-cool dark
div.ui-widget-header {
background: #777;
box-shadow: inset 0 1px 0 #999;
.ui-widget-header {
background: rgb(142, 62, 99); // UXPL secondary dark
box-shadow: none;
}
div.ui-corner-all.slider-range {
background-color: #1e91d3;
.ui-corner-all.slider-range {
opacity: 0.3;
background-color: #1e91d3;
}
a.ui-slider-handle {
.ui-slider-handle {
@extend %ui-fake-link;
@include transform(scale(.7, 1.3) translate3d(-80%, -15%, 0));
background: $pink url('#{$static-path}/images/slider-handle.png') center center no-repeat;
background-size: 50%;
border: 1px solid darken($pink, 20%);
border-radius: 50%;
box-shadow: inset 0 1px 0 lighten($pink, 10%);
height: 20px;
margin-left: 0;
@include transform-origin(bottom left);
@include transition(all .7s ease-in-out 0s);
top: 0;
-webkit-transition: -webkit-transform 0.7s ease-in-out;
-moz-transition: -moz-transform 0.7s ease-in-out;
-ms-transition: -ms-transform 0.7s ease-in-out;
transition: transform 0.7s ease-in-out;
width: 20px;
height: ($baseline / 4);
width: ($baseline / 4);
margin-left: -($baseline / 8); // center-center causes the control to be beyond the end of the sider
border: 0;
border-radius: ($baseline / 5);
background: rgb(203, 89, 141); // UXPL secondary base
box-shadow: none;
&:focus, &:hover {
background-color: lighten($pink, 10%);
&:focus,
&:hover {
background-color: rgb(219, 139, 175); // UXPL secondary light
}
}
}
.vcr {
float: left;
@include float(left);
list-style: none;
margin: 0 lh() 0 0;
@include border-right(1px solid rgb(40, 44, 46)); // UXPL grayscale-cool x-dark
padding: 0;
@media (max-width: 1120px) {
margin-right: lh(0.5);
@include margin-right(lh(0.5));
font-size: em(14);
}
.video_control {
@extend %video-button;
float: left;
background-image: url('#{$static-path}/images/vcr.png');
background-position: 15px 15px ;
background-repeat: no-repeat;
border-left: none;
padding: 0 lh(.75);
width: 14px;
&:focus {
@extend %ui-depth4;
position: relative;
outline: $white dotted thin;
outline-offset: -2px;
}
&:empty {
height: 46px;
background-position: 15px 15px;
}
&.play {
background-position: 17px -114px;
}
&.pause {
background-position: 16px -50px;
}
&.skip {
background-image: none;
text-indent: 0;
width: initial;
white-space: nowrap;
}
}
div.vidtime {
.vidtime {
@extend %t-strong;
float: left;
line-height: 46px; //height of play pause buttons
-webkit-font-smoothing: antialiased;
padding-left: lh(.75);
@extend %t-title7;
@include padding-left(lh(.75));
display: inline-block;
color: rgb(207, 216, 220); // UXPL grayscale-cool light
-webkit-font-smoothing: antialiased;;
@media (max-width: 1120px) {
padding-left: lh(0.5);
@include padding-left(lh(0.5));
}
}
}
div.secondary-controls {
float: right;
.secondary-controls {
@include float(right);
@include border-left(1px dotted rgb(79, 89, 93)); // UXPL grayscale-cool x-dark
.volume,
.add-fullscreen,
.grouped-controls,
.quality-control {
@include border-left(1px dotted rgb(79, 89, 93)); // UXPL grayscale-cool x-dark
}
.speed-button,
.volume > .control,
.add-fullscreen,
.quality-control,
.toggle-transcript {
a.speed-button,
div.volume > a,
a.add-fullscreen,
a.quality-control,
a.hide-subtitles {
// overflow is used to bypass Firefox CSS :focus outline bug
// http://johndoesdesign.com/blog/2012/css/firefox-and-its-css-focus-outline-bug/
&:focus {
@extend %ui-depth5;
position: relative;
outline: $white dotted thin;
outline-offset: -2px;
overflow: auto;
}
}
.menu-container {
float: left;
position: relative;
&.is-opened {
.menu {
display: block;
opacity: 1;
padding: 0;
margin: 0;
list-style: none;
}
}
.menu {
@include transition(none);
@extend %ui-depth1;
box-shadow: inset 1px 0 0 #555, 0 1px 0 #444;
background-color: #444;
border: 1px solid $black;
bottom: 46px;
display: none;
opacity: 0;
position: absolute;
display: none;
bottom: ($baseline * 2);
@include right(0); // right-align menus since this whole collection is on the right
width: 120px;
margin: 0;
border: none;
padding: 0;
box-shadow: none;
background-color: rgb(40, 44, 46); // UXPL grayscale-cool x-dark
list-style: none;
li {
@extend %ui-fake-link;
box-shadow: 0 1px 0 #555;
border-bottom: 1px solid $black;
color: $white;
color: rgb(231, 236, 238); // UXPL grayscale-cool x-light
a {
border: 0;
color: $white;
.speed-option,
.control-lang {
@include text-align(left);
display: block;
width: 100%;
border: 0;
border-radius: 0;
padding: lh(0.5);
background: rgb(40, 44, 46); // UXPL grayscale-cool x-dark
box-shadow: none;
color: rgb(231, 236, 238); // UXPL grayscale-cool x-light
overflow: hidden;
text-shadow: none;
text-overflow: ellipsis;
white-space: nowrap;
&:hover, &:focus {
background-color: #666;
color: #aaa;
outline-offset: -4px;
&:hover,
&:focus {
background-color: rgb(79, 89, 93); // UXPL grayscale-cool dark
color: rgb(252, 252, 252); // UXPL grayscale white
}
}
&.is-active{
a {
font-weight: bold;
}
}
&.is-active {
&:last-child {
box-shadow: none;
border-bottom: 0;
margin-top: 0;
.speed-option,
.control-lang {
color: rgb(14, 166, 236); // UXPL primary accent
}
}
}
}
div.speeds {
&.is-opened {
.speed-button {
background-image: url('#{$static-path}/images/open-arrow.png');
.menu {
display: block;
}
}
}
.speeds,
.lang,
.grouped-controls {
display: inline-block;
.menu{
width: 131px;
.control {
@media (max-width: 1120px) {
width: 80px;
.icon-fallback-img {
@include float(left);
@include transform-origin(center center);
}
}
}
.speed-button {
@extend %video-button;
@include clearfix();
background-image: url('#{$static-path}/images/closed-arrow.png');
background-position: 10px center;
background-repeat: no-repeat;
min-width: 116px;
text-indent: 0;
.speeds {
@media (max-width: 1120px) {
min-width: 0;
width: 60px;
&.is-opened {
.control {
.icon {
@include ltr {
@include transform(rotate(-90deg));
}
@include rtl {
@include transform(rotate(90deg));
}
}
}
}
.speed-button {
.label {
float: left;
font-size: em(14);
font-weight: normal;
letter-spacing: 1px;
padding: 0 lh(0.25) 0 lh(0.5);
line-height: 46px;
text-transform: uppercase;
color: #999;
@include padding(0 ($baseline/3) 0 0);
font-family: $body-font-family;
color: rgb(231, 236, 238); // UXPL grayscale-cool x-light
@media (max-width: 1120px) {
display: none;
......@@ -487,117 +550,115 @@ div.video {
}
.value {
float: left;
@include padding(0, lh(0.5), 0, 0);
color: rgb(231, 236, 238); // UXPL grayscale-cool x-light
font-weight: bold;
margin-bottom: 0;
padding: 0 lh(0.5) 0 0;
@media (max-width: 1120px) {
padding: 0 lh(0.5) 0 lh(0.5);
padding: 0 lh(0.5);
}
}
}
}
line-height: 46px;
color: $white;
.lang {
.language-menu {
width: $baseline;
padding: ($baseline / 2) 0;
}
.control {
.icon {
@include rtl {
@include transform(rotate(-180deg));
}
}
}
div.volume {
float: left;
&.is-opened {
.control {
.icon {
@include ltr {
@include transform(rotate(90deg));
}
@include rtl {
@include transform(rotate(90deg));
}
}
}
}
}
.volume {
display: inline-block;
position: relative;
&.is-opened {
.volume-slider-container {
display: block;
opacity: 1;
}
}
&.is-muted {
& > a {
background-image: url('#{$static-path}/images/mute.png');
}
}
& > a {
@extend %video-button;
@include clearfix();
background-image: url('#{$static-path}/images/volume.png');
background-position: 10px center;
background-repeat: no-repeat;
width: 30px;
height: 46px;
}
&:not(:first-child) > a {
border-left: none;
@include border-left(none);
}
.volume-slider-container {
@include transition(none);
@extend %ui-depth1;
box-shadow: inset 1px 0 0 #555, 0 3px 0 #444;
background-color: #444;
border: 1px solid $black;
bottom: 46px;
display: none;
opacity: 0;
position: absolute;
width: 45px;
height: 125px;
margin-left: -1px;
bottom: ($baseline * 2);
@include right(0);
width: 41px;
height: 120px;
background-color: rgb(40, 44, 46); // UXPL grayscale-cool x-dark
.volume-slider {
height: 100px;
border: 0;
width: 5px;
width: ($baseline / 4);
margin: 14px auto;
background: #666;
border: 1px solid $black;
box-shadow: 0 1px 0 #333;
border: 0;
background: rgb(79, 89, 93); // UXPL grayscale-cool dark
a.ui-slider-handle {
.ui-slider-handle {
@extend %ui-fake-link;
@include transition(height $tmg-s2 ease-in-out 0s, width $tmg-s2 ease-in-out 0s);
background: $pink url('#{$static-path}/images/slider-handle.png') center center no-repeat;
background-size: 50%;
border: 1px solid darken($pink, 20%);
border-radius: 15px;
box-shadow: inset 0 1px 0 lighten($pink, 10%);
@include left(-5px);
height: 15px;
left: -6px;
width: 15px;
background: rgb(203, 89, 141); // UXPL secondary base
border: 0;
border-radius: ($baseline / 5);
&:hover,
&:focus {
background: rgb(219, 139, 175); // UXPL secondary light
}
}
.ui-slider-range {
background: #ddd;
background: rgb(142, 62, 99); // UXPL secondary dark
}
}
}
}
a.add-fullscreen {
@extend %video-button;
background: url('#{$static-path}/images/fullscreen.png') center no-repeat;
border-left: none;
float: left;
padding: 0 11px;
width: 30px;
}
a.quality-control {
@extend %video-button;
background: url('#{$static-path}/images/hd.png') center no-repeat;
border-left: none;
float: left;
padding: 0 11px;
width: 30px;
.quality-control {
font-weight: 700;
letter-spacing: -1px;
&.active {
background-color: #F44;
color: #0ff;
text-decoration: none;
color: rgb(14, 166, 236); // UXPL primary accent
}
&.is-hidden {
......@@ -605,62 +666,55 @@ div.video {
}
}
div.lang {
& > a.hide-subtitles {
@extend %video-button;
@include transition(none);
box-shadow: inset 1px 0 0 #555;
background: url('#{$static-path}/images/cc.png') center no-repeat;
border-left: none;
border-right: none;
padding: 0 11px;
width: 30px;
.toggle-transcript {
&.off {
opacity: 0.7;
&.is-active {
color: rgb(14, 166, 236); // UXPL primary accent
}
}
.menu.langs-list {
right: -1px;
width: 150px;
.lang {
& > .hide-subtitles {
@include transition(none);
}
}
}
}
&:hover section.video-controls {
ul, div {
opacity: 1;
}
&:hover {
div.slider {
@include transform(scaleY(1) translate3d(0, 0, 0));
.video-controls {
.slider {
height: ($baseline / 1.5);
a.ui-slider-handle {
@include transform(scale(1) translate3d(-50%, -15%, 0));
.ui-slider-handle {
height: ($baseline / 1.5);
width: ($baseline / 1.5);
}
}
}
}
}
ol.subtitles {
padding-left: 0;
float: left;
max-height: 460px;
.subtitles {
@include float(left);
overflow: auto;
width: flex-grid(3, 9);
margin: 0;
max-height: 460px;
width: flex-grid(3, 9);
padding: 0;
font-size: 14px;
list-style: none;
visibility: visible;
li {
@extend %ui-fake-link;
border: 0;
color: rgb(29,157,217);
margin-bottom: 8px;
border: 0;
padding: 0;
color: #0074b5; // AA compliant
line-height: lh();
&.current {
......@@ -673,7 +727,8 @@ div.video {
outline-offset: -1px;
}
&:hover, &:focus {
&:hover,
&:focus {
text-decoration: underline;
}
......@@ -685,13 +740,12 @@ div.video {
&.closed {
article.video-wrapper {
.video-wrapper {
width: flex-grid(9,9);
background-color: inherit;
}
article.video-wrapper section.video-controls.html5 {
.video-wrapper .video-controls.html5 {
bottom: 0;
left: 0;
right: 0;
......@@ -699,21 +753,22 @@ div.video {
z-index: 1;
}
article.video-wrapper div.video-player-pre, article.video-wrapper div.video-player-post {
.video-wrapper .video-player-pre,
.video-wrapper .video-player-post {
height: 0;
}
article.video-wrapper section.video-player {
.video-wrapper .video-player {
h3 {
color: black;
}
}
ol.subtitles {
.subtitles {
@extend .is-hidden;
}
ol.subtitles.html5 {
.subtitles.html5 {
@extend %ui-depth0;
background-color: rgba(243, 243, 243, 0.8);
height: 100%;
......@@ -743,63 +798,66 @@ div.video {
border-radius: 0;
&.closed {
div.tc-wrapper {
article.video-wrapper {
.tc-wrapper {
.video-wrapper {
width: 100%;
}
}
}
article.video-wrapper div.video-player-pre, article.video-wrapper div.video-player-post {
.video-wrapper .video-player-pre,
.video-wrapper .video-player-post {
height: 0;
}
article.video-wrapper {
.video-wrapper {
position: static;
}
article.video-wrapper section.video-player {
.video-wrapper .video-player {
h3 {
color: white;
}
}
div.tc-wrapper {
.tc-wrapper {
@include clearfix();
width: 100%;
height: 100%;
position: static;
article.video-wrapper {
.video-wrapper {
height: 100%;
width: 75%;
@include margin-right(0);
vertical-align: middle;
margin-right: 0;
object, iframe, video{
object,
iframe,
video{
position: absolute;
width: auto;
height: auto;
}
}
section.video-controls {
.video-controls {
@extend %ui-depth4;
position: absolute;
bottom: 0;
left: 0;
position: absolute;
width: 100%;
}
}
ol.subtitles {
@include box-sizing(border-box);
@include transition(none);
background: $black;
.subtitles {
height: 100%;
width: 25%;
padding: lh();
@include box-sizing(border-box);
@include transition(none);
background: $black;
visibility: visible;
li {
......@@ -813,9 +871,11 @@ div.video {
}
&.is-touch {
div.tc-wrapper {
article.video-wrapper {
object, iframe, video {
.tc-wrapper {
.video-wrapper {
object,
iframe,
video {
width: 100%;
height: 100%;
}
......@@ -864,5 +924,3 @@ div.video {
}
}
}
......@@ -260,7 +260,7 @@
state.videoSpeedControl.setSpeed(1.0);
spyOn(state.videoPlayer, 'onSpeedChange').andCallThrough();
$('li[data-speed="0.75"] a').click();
$('li[data-speed="0.75"] .speed-link').click();
});
it('trigger speedChange event', function () {
......@@ -274,7 +274,7 @@
xdescribe('onSpeedChange', function () {
beforeEach(function () {
state = jasmine.initializePlayer();
$('li[data-speed="1.0"] a').addClass('active');
$('li[data-speed="1.0"] .speed-link').addClass('active');
state.videoSpeedControl.setSpeed(0.75);
});
......
......@@ -23,39 +23,39 @@
});
describe('constructor', function () {
describe('always', function () {
beforeEach(function () {
spyOn($, 'ajaxWithPrefix').andCallThrough();
});
it('create the caption element', function () {
it('create the transcript element', function () {
state = jasmine.initializePlayer();
expect($('.video')).toContain('ol.subtitles');
expect($('.video')).toContain('.subtitles');
});
it('add caption control to video player', function () {
it('add transcript control to video player', function () {
state = jasmine.initializePlayer();
expect($('.video')).toContain('a.hide-subtitles');
expect($('.video')).toContain('.toggle-transcript');
});
it('add ARIA attributes to caption control', function () {
it('add ARIA attributes to transcript control', function () {
state = jasmine.initializePlayer();
var captionControl = $('a.hide-subtitles');
var captionControl = $('.toggle-transcript');
expect(captionControl).toHaveAttrs({
'role': 'button',
'title': 'Turn off captions',
'aria-disabled': 'false'
});
});
it('fetch the caption in HTML5 mode', function () {
it('fetch the transcript in HTML5 mode', function () {
runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () {
return state.videoCaption.loaded;
}, 'Expect captions to be loaded.', WAIT_TIMEOUT);
}, 'Expect transcript to be loaded.', WAIT_TIMEOUT);
runs(function () {
expect($.ajaxWithPrefix).toHaveBeenCalledWith({
......@@ -70,7 +70,7 @@
});
});
it('fetch the caption in Flash mode', function () {
it('fetch the transcript in Flash mode', function () {
runs(function () {
state = jasmine.initializePlayerYouTube();
spyOn(state, 'isFlashMode').andReturn(true);
......@@ -79,7 +79,7 @@
waitsFor(function () {
return state.videoCaption.loaded;
}, 'Expect captions to be loaded.', WAIT_TIMEOUT);
}, 'Expect transcript to be loaded.', WAIT_TIMEOUT);
runs(function () {
expect($.ajaxWithPrefix).toHaveBeenCalledWith({
......@@ -96,14 +96,14 @@
});
});
it('fetch the caption in Youtube mode', function () {
it('fetch the transcript in Youtube mode', function () {
runs(function () {
state = jasmine.initializePlayerYouTube();
});
waitsFor(function () {
return state.videoCaption.loaded;
}, 'Expect captions to be loaded.', WAIT_TIMEOUT);
}, 'Expect transcript to be loaded.', WAIT_TIMEOUT);
runs(function () {
expect($.ajaxWithPrefix).toHaveBeenCalledWith({
......@@ -159,7 +159,14 @@
});
describe('renderLanguageMenu', function () {
describe('is rendered', function () {
var KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', { keyCode: key });
};
it('if languages more than 1', function () {
state = jasmine.initializePlayer();
var transcripts = state.config.transcriptLanguages,
......@@ -172,7 +179,7 @@
$('.langs-list li').each(function(index) {
var code = $(this).data('lang-code'),
link = $(this).find('a'),
link = $(this).find('.control'),
label = link.text();
expect(code).toBeInArray(langCodes);
......@@ -183,7 +190,7 @@
it('when clicking on link with new language', function () {
state = jasmine.initializePlayer();
var Caption = state.videoCaption,
link = $('.langs-list li[data-lang-code="de"] a');
link = $('.langs-list li[data-lang-code="de"] .control-lang');
spyOn(Caption, 'fetchCaption');
spyOn(state.storage, 'setItem');
......@@ -201,7 +208,7 @@
it('when clicking on link with current language', function () {
state = jasmine.initializePlayer();
var Caption = state.videoCaption,
link = $('.langs-list li[data-lang-code="en"] a');
link = $('.langs-list li[data-lang-code="en"] .control-lang');
spyOn(Caption, 'fetchCaption');
spyOn(state.storage, 'setItem');
......@@ -223,6 +230,23 @@
$('.lang').mouseleave();
expect($('.lang')).not.toHaveClass('is-opened');
});
it('opens the language menu on arrow up', function() {
state = jasmine.initializePlayer();
$('.language-menu').focus();
$('.language-menu').trigger(keyPressEvent(KEY.UP));
expect($('.lang')).toHaveClass('is-opened');
expect($('.langs-list').find('li').last().find('.control-lang')).toBeFocused();
});
it('closes the language menu on ESC', function() {
state = jasmine.initializePlayer();
$('.language-menu').trigger(keyPressEvent(KEY.UP));
expect($('.lang')).toHaveClass('is-opened');
$('.language-menu').trigger(keyPressEvent(KEY.ESCAPE));
expect($('.lang')).not.toHaveClass('is-opened');
expect($('.language-menu')).toBeFocused();
});
});
describe('is not rendered', function () {
......@@ -246,10 +270,10 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
it('render the caption', function () {
it('render the transcript', function () {
runs(function () {
var captionsData = jasmine.stubbedCaption,
items = $('.subtitles li[data-index]');
......@@ -267,7 +291,7 @@
});
});
it('add a padding element to caption', function () {
it('add a padding element to transcript', function () {
runs(function () {
expect($('.subtitles li:first').hasClass('spacing'))
.toBe(true);
......@@ -277,7 +301,7 @@
});
it('bind all the caption link', function () {
it('bind all the transcript link', function () {
runs(function () {
var handlerList = ['captionMouseOverOut', 'captionClick',
'captionMouseDown', 'captionFocus', 'captionBlur',
......@@ -323,7 +347,7 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
runs(function () {
expect(state.videoCaption.rendered).toBeTruthy();
......@@ -346,14 +370,14 @@
);
});
it('show captions on play', function () {
it('show transcript on play', function () {
runs(function () {
state.el.trigger('play');
});
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
runs(function () {
var captionsData = jasmine.stubbedCaption,
......@@ -377,7 +401,7 @@
});
});
describe('when no captions file was specified', function () {
describe('when no transcripts file was specified', function () {
beforeEach(function () {
state = jasmine.initializePlayer('video_all.html', {
'sub': '',
......@@ -385,8 +409,8 @@
});
});
it('captions panel is not shown', function () {
expect(state.videoCaption.hideSubtitlesEl).toBeHidden();
it('transcript panel is not shown', function () {
expect(state.videoCaption.languageChooserEl).toBeHidden();
});
});
});
......@@ -403,10 +427,10 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
describe('when cursor is outside of the caption box', function () {
describe('when cursor is outside of the transcript box', function () {
it('does not set freezing timeout', function () {
runs(function () {
expect(state.videoCaption.frozen).toBeFalsy();
......@@ -414,7 +438,7 @@
});
});
describe('when cursor is in the caption box', function () {
describe('when cursor is in the transcript box', function () {
beforeEach(function () {
spyOn(state.videoCaption, 'onMouseLeave');
runs(function () {
......@@ -452,7 +476,7 @@
});
describe(
'when cursor is moving out of the caption box',
'when cursor is moving out of the transcript box',
function () {
beforeEach(function () {
......@@ -469,7 +493,7 @@
expect(window.clearTimeout).toHaveBeenCalledWith(100);
});
it('unfreeze the caption', function () {
it('unfreeze the transcript', function () {
expect(state.videoCaption.frozen).toBeNull();
});
});
......@@ -482,7 +506,7 @@
$('.subtitles').trigger(jQuery.Event('mouseout'));
});
it('scroll the caption', function () {
it('scroll the transcript', function () {
expect($.fn.scrollTo).toHaveBeenCalled();
});
});
......@@ -493,7 +517,7 @@
$('.subtitles').trigger(jQuery.Event('mouseout'));
});
it('does not scroll the caption', function () {
it('does not scroll the transcript', function () {
expect($.fn.scrollTo).not.toHaveBeenCalled();
});
});
......@@ -514,7 +538,7 @@
spyOn(state, 'youtubeId').andReturn('Z5KLxerq05Y');
});
it('show caption on language change', function () {
it('show transcript on language change', function () {
Caption.loaded = true;
Caption.fetchCaption();
......@@ -522,7 +546,7 @@
expect(Caption.hideCaptions).toHaveBeenCalledWith(false);
});
msg = 'use cookie to show/hide captions if they have not been ' +
msg = 'use cookie to show/hide transcripts if they have not been ' +
'loaded yet';
it(msg, function () {
Caption.loaded = false;
......@@ -554,7 +578,7 @@
});
msg = 'on success: change language on touch devices when ' +
'captions have not been rendered yet';
'transcripts have not been rendered yet';
it(msg, function () {
state.isTouch = true;
Caption.loaded = true;
......@@ -604,7 +628,7 @@
expect(Caption.loaded).toBeTruthy();
});
msg = 'on error: captions are hidden if there are no transcripts';
msg = 'on error: transcripts are hidden if there are no transcripts';
it(msg, function () {
spyOn(Caption, 'fetchAvailableTranslations');
$.ajax.andCallFake(function (settings) {
......@@ -619,7 +643,6 @@
expect(Caption.fetchAvailableTranslations).not.toHaveBeenCalled();
expect(Caption.hideCaptions.mostRecentCall.args)
.toEqual([true, false]);
expect(Caption.hideSubtitlesEl).toBeHidden();
});
msg = 'on error: for Html5 player an attempt to fetch transcript ' +
......@@ -667,7 +690,7 @@
msg = 'on error: fetch available translations if there are ' +
'additional transcripts';
xit(msg, function () {
it(msg, function () {
$.ajax
.andCallFake(function (settings) {
_.result(settings, 'error');
......@@ -683,7 +706,6 @@
expect($.ajaxWithPrefix).toHaveBeenCalled();
expect(Caption.fetchAvailableTranslations).toHaveBeenCalled();
expect(Caption.hideCaptions).not.toHaveBeenCalled();
});
});
......@@ -745,7 +767,7 @@
expect(Caption.renderLanguageMenu).not.toHaveBeenCalled();
});
msg = 'on error: captions are hidden if there are no transcript';
msg = 'on error: transcripts are hidden if there are no transcript';
it(msg, function () {
$.ajax.andCallFake(function (settings) {
_.result(settings, 'error');
......@@ -754,12 +776,12 @@
expect($.ajaxWithPrefix).toHaveBeenCalled();
expect(Caption.hideCaptions).toHaveBeenCalledWith(true, false);
expect(Caption.hideSubtitlesEl).toBeHidden();
expect(Caption.subtitlesEl).toBeHidden();
});
});
describe('play', function () {
describe('when the caption was not rendered', function () {
describe('when the transcript was not rendered', function () {
beforeEach(function () {
window.onTouchBasedDevice.andReturn(['iPad']);
......@@ -770,10 +792,10 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
it('render the caption', function () {
it('render the transcript', function () {
runs(function () {
var captionsData;
......@@ -792,7 +814,7 @@
});
it('add a padding element to caption', function () {
it('add a padding element to transcript', function () {
runs(function () {
expect($('.subtitles li:first')).toBe('.spacing');
expect($('.subtitles li:last')).toBe('.spacing');
......@@ -833,7 +855,7 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
describe('when the video speed is 1.0x', function () {
......@@ -852,7 +874,7 @@
});
describe('when the video speed is not 1.0x', function () {
it('search the caption based on 1.0x speed', function () {
it('search the transcript based on 1.0x speed', function () {
runs(function () {
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(5);
......@@ -882,14 +904,14 @@
});
});
it('deactivate the previous caption', function () {
it('deactivate the previous transcript', function () {
runs(function () {
expect($('.subtitles li[data-index=1]'))
.not.toHaveClass('current');
});
});
it('activate new caption', function () {
it('activate new transcript', function () {
runs(function () {
expect($('.subtitles li[data-index=5]'))
.toHaveClass('current');
......@@ -902,7 +924,7 @@
});
});
it('scroll caption to new position', function () {
it('scroll transcript to new position', function () {
runs(function () {
expect($.fn.scrollTo).toHaveBeenCalled();
});
......@@ -930,7 +952,7 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
runs(function () {
videoControl = state.videoControl;
......@@ -939,8 +961,8 @@
});
});
describe('set the height of caption container', function () {
it('when CC button is enabled', function () {
describe('set the height of transcript container', function () {
it('when transcript button is enabled', function () {
runs(function () {
var realHeight = parseInt(
$('.subtitles').css('maxHeight'), 10
......@@ -953,7 +975,7 @@
});
});
it('when CC button is disabled ', function () {
it('when transcript button is disabled ', function () {
runs(function () {
var realHeight, videoWrapperHeight, progressSliderHeight,
controlHeight, shouldBeHeight;
......@@ -976,7 +998,7 @@
});
});
it('set the height of caption spacing', function () {
it('set the height of transcript spacing', function () {
runs(function () {
var firstSpacing, lastSpacing;
......@@ -994,7 +1016,7 @@
});
});
it('scroll caption to new position', function () {
it('scroll transcript to new position', function () {
runs(function () {
expect($.fn.scrollTo).toHaveBeenCalled();
});
......@@ -1009,11 +1031,11 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
describe('when frozen', function () {
it('does not scroll the caption', function () {
it('does not scroll the transcript', function () {
runs(function () {
state.videoCaption.frozen = true;
$('.subtitles li[data-index=1]').addClass('current');
......@@ -1030,8 +1052,8 @@
});
});
describe('when there is no current caption', function () {
it('does not scroll the caption', function () {
describe('when there is no current transcript', function () {
it('does not scroll the transcript', function () {
runs(function () {
state.videoCaption.scrollCaption();
expect($.fn.scrollTo).not.toHaveBeenCalled();
......@@ -1039,8 +1061,8 @@
});
});
describe('when there is a current caption', function () {
it('scroll to current caption', function () {
describe('when there is a current transcript', function () {
it('scroll to current transcript', function () {
runs(function () {
$('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.scrollCaption();
......@@ -1062,7 +1084,7 @@
isRendered = state.videoCaption.rendered;
return isRendered && duration;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
describe('when the video speed is 1.0x', function () {
......@@ -1104,40 +1126,30 @@
$('.subtitles li[data-index=1]').addClass('current');
});
describe('when the caption is visible', function () {
describe('when the transcript is visible', function () {
beforeEach(function () {
state.el.removeClass('closed');
state.videoCaption.toggle(jQuery.Event('click'));
});
it('hide the caption', function () {
it('hide the transcript', function () {
expect(state.el).toHaveClass('closed');
});
it('changes ARIA attribute of caption control', function () {
expect($('a.hide-subtitles'))
.toHaveAttr('title', 'Turn on captions');
});
});
describe('when the caption is hidden', function () {
describe('when the transcript is hidden', function () {
beforeEach(function () {
state.el.addClass('closed');
state.videoCaption.toggle(jQuery.Event('click'));
jasmine.Clock.useMock();
});
it('show the caption', function () {
it('show the transcript', function () {
expect(state.el).not.toHaveClass('closed');
});
it('changes ARIA attribute of caption control', function () {
expect($('a.hide-subtitles'))
.toHaveAttr('title', 'Turn off captions');
});
// Test turned off due to flakiness (11/25/13)
xit('scroll the caption', function () {
xit('scroll the transcript', function () {
// After transcripts are shown, and the video plays for a
// bit.
jasmine.Clock.tick(1000);
......@@ -1153,7 +1165,7 @@
});
});
describe('caption accessibility', function () {
describe('transcript accessibility', function () {
beforeEach(function () {
runs(function () {
state = jasmine.initializePlayer();
......@@ -1161,7 +1173,7 @@
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}, 'Transcripts are not rendered', WAIT_TIMEOUT);
});
describe('when getting focus through TAB key', function () {
......@@ -1174,7 +1186,7 @@
});
});
it('shows an outline around the caption', function () {
it('shows an outline around the transcript', function () {
runs(function () {
expect($('.subtitles li[data-index=0]'))
.toHaveClass('focused');
......@@ -1197,7 +1209,7 @@
});
});
it('does not show an outline around the caption', function () {
it('does not show an outline around the transcript', function () {
runs(function () {
expect($('.subtitles li[data-index=0]'))
.not.toHaveClass('focused');
......@@ -1212,7 +1224,7 @@
});
describe(
'when same caption gets the focus through mouse after ' +
'when same transcript gets the focus through mouse after ' +
'having focus through TAB key',
function () {
......@@ -1241,7 +1253,7 @@
});
describe(
'when a second caption gets focus through mouse after ' +
'when a second transcript gets focus through mouse after ' +
'first had focus through TAB key',
function () {
......
......@@ -5,16 +5,16 @@
closeSubmenuKeyboard, menu, menuItems, menuSubmenuItem, submenu, submenuItems, overlay, playButton;
openMenu = function () {
var container = $('div.video');
var container = $('.video');
jasmine.Clock.useMock();
container.find('video').trigger('contextmenu');
menu = container.children('ol.contextmenu');
menuItems = menu.children('li.menu-item').not('.submenu-item');
menuSubmenuItem = menu.children('li.menu-item.submenu-item');
submenu = menuSubmenuItem.children('ol.submenu');
submenuItems = submenu.children('li.menu-item');
overlay = container.children('div.overlay');
playButton = $('a.video_control.play');
menu = container.children('.contextmenu');
menuItems = menu.children('.menu-item').not('.submenu-item');
menuSubmenuItem = menu.children('.menu-item.submenu-item');
submenu = menuSubmenuItem.children('.submenu');
submenuItems = submenu.children('.menu-item');
overlay = container.children('.overlay');
playButton = $('.video_control.play');
};
keyPressEvent = function(key) {
......
......@@ -30,8 +30,6 @@
var fullScreenControl = $('.add-fullscreen');
expect(fullScreenControl).toHaveAttrs({
'role': 'button',
'title': 'Fill browser',
'aria-disabled': 'false'
});
});
......@@ -53,14 +51,10 @@
var fullScreenControl = $('.add-fullscreen');
fullScreenControl.click();
expect(fullScreenControl).toHaveAttrs({
'role': 'button',
'title': 'Exit full browser',
'aria-disabled': 'false'
});
fullScreenControl.click();
expect(fullScreenControl).toHaveAttrs({
'role': 'button',
'title': 'Fill browser',
'aria-disabled': 'false'
});
});
......
......@@ -25,8 +25,6 @@
it('add ARIA attributes to play control', function () {
expect($('.video_control.play')).toHaveAttrs({
'role': 'button',
'title': 'Play',
'aria-disabled': 'false'
});
});
......@@ -34,8 +32,6 @@
it('can update ARIA state on play', function () {
state.el.trigger('play');
expect($('.video_control.pause')).toHaveAttrs({
'role': 'button',
'title': 'Pause',
'aria-disabled': 'false'
});
});
......@@ -44,8 +40,6 @@
state.el.trigger('play');
state.el.trigger('ended');
expect($('.video_control.play')).toHaveAttrs({
'role': 'button',
'title': 'Play',
'aria-disabled': 'false'
});
});
......
......@@ -27,8 +27,6 @@
it('add ARIA attributes to play control', function () {
expect($('.video_control.play')).toHaveAttrs({
'role': 'button',
'title': 'Play',
'aria-disabled': 'false'
});
});
......
......@@ -745,11 +745,6 @@ function (VideoPlayer) {
$('.add-fullscreen').click();
});
it('replace the full screen button tooltip', function () {
expect($('.add-fullscreen'))
.toHaveAttr('title', 'Exit full browser');
});
it('add the video-fullscreen class', function () {
expect(state.el).toHaveClass('video-fullscreen');
});
......@@ -773,11 +768,6 @@ function (VideoPlayer) {
$('.add-fullscreen').click();
});
it('replace the full screen button tooltip', function () {
expect($('.add-fullscreen'))
.toHaveAttr('title', 'Fill browser');
});
it('remove the video-fullscreen class', function () {
expect(state.el).not.toHaveClass('video-fullscreen');
});
......
......@@ -33,8 +33,6 @@
it('add ARIA attributes to quality control', function () {
expect(qualityControl.el).toHaveAttrs({
'role': 'button',
'title': 'HD off',
'aria-disabled': 'false'
});
});
......@@ -117,7 +115,7 @@
it('does not contain the quality control', function () {
state = jasmine.initializePlayer();
expect(state.el.find('a.quality-control').length).toBe(0);
expect(state.el.find('.quality-control').length).toBe(0);
});
});
});
......
......@@ -33,8 +33,6 @@
it('add ARIA attributes to play control', function () {
state.el.trigger('play');
expect($('.skip-control')).toHaveAttrs({
'role': 'button',
'title': 'Do not show again',
'aria-disabled': 'false'
});
});
......
(function (undefined) {
'use strict';
describe('VideoSpeedControl', function () {
var state, oldOTBD;
......@@ -38,21 +39,11 @@
expect($(link)).toHaveData(
'speed', state.speeds[index]
);
expect($(link).find('a').text()).toBe(
expect($(link).find('.speed-option').text()).toBe(
state.speeds[index] + 'x'
);
});
});
it('add ARIA attributes to speed control', function () {
var speedControl = $('div.speeds>a');
expect(speedControl).toHaveAttrs({
'role': 'button',
'title': 'Speeds',
'aria-disabled': 'false'
});
});
});
describe('when running on touch based device', function () {
......@@ -61,33 +52,17 @@
window.onTouchBasedDevice.andReturn([device]);
state = jasmine.initializePlayer();
expect(state.el.find('div.speeds')).not.toExist();
expect(state.el.find('.speeds')).not.toExist();
});
});
});
describe('when running on non-touch based device', function () {
var speedControl, speedEntries, speedButton,
var speedControl, speedEntries, speedButton, speedsContainer,
KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', {keyCode: key});
},
// Get previous element in array or cyles back to the last
// if it is the first.
previousSpeed = function(index) {
return speedEntries.eq(index < 1 ?
speedEntries.length - 1 :
index - 1);
},
// Get next element in array or cyles back to the first if
// it is the last.
nextSpeed = function(index) {
return speedEntries.eq(index >= speedEntries.length-1 ?
0 :
index + 1);
};
beforeEach(function () {
......@@ -95,7 +70,7 @@
speedControl = $('.speeds');
speedButton = $('.speed-button');
speedsContainer = $('.video-speeds');
speedEntries = speedsContainer.find('a');
speedEntries = speedsContainer.find('.speed-option');
});
it('open/close the speed menu on mouseenter/mouseleave',
......@@ -114,11 +89,6 @@
expect(speedControl).toHaveClass('is-opened');
});
it('close the speed menu on click', function () {
speedControl.mouseenter().click();
expect(speedControl).not.toHaveClass('is-opened');
});
it('close the speed menu on outside click', function () {
speedControl.trigger(keyPressEvent(KEY.ENTER));
$(window).click();
......@@ -150,8 +120,7 @@
it('UP and DOWN keydown function as expected on speed entries',
function () {
var lastEntry = speedEntries.length-1,
speed_0_75 = speedEntries.filter(':contains("0.75x")'),
var speed_0_75 = speedEntries.filter(':contains("0.75x")'),
speed_1_0 = speedEntries.filter(':contains("1.0x")');
// First open menu
......@@ -226,7 +195,7 @@
it('trigger speedChange event', function () {
spyOnEvent(state.el, 'speedchange');
$('li[data-speed="0.75"] a').click();
$('li[data-speed="0.75"] .speed-option').click();
expect('speedchange').toHaveBeenTriggeredOn(state.el);
expect(state.videoSpeedControl.currentSpeed).toEqual('0.75');
});
......
......@@ -3,6 +3,12 @@
describe('VideoVolumeControl', function () {
var state, oldOTBD, volumeControl;
var KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', { keyCode: key });
};
beforeEach(function () {
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
......@@ -56,24 +62,20 @@ describe('VideoVolumeControl', function () {
var liveRegion = $('.video-live-region');
expect(liveRegion).toHaveAttrs({
'role': 'status',
'aria-live': 'polite',
'aria-atomic': 'false'
'aria-live': 'polite'
});
});
it('add ARIA attributes to volume control', function () {
var button = $('.volume > a');
var button = $('.volume .control');
expect(button).toHaveAttrs({
'role': 'button',
'title': 'Volume',
'aria-disabled': 'false'
});
});
it('bind the volume control', function () {
var button = $('.volume > a');
var button = $('.volume .control');
expect(button).toHandle('keydown');
expect(button).toHandle('mousedown');
......@@ -185,16 +187,19 @@ describe('VideoVolumeControl', function () {
});
describe('increaseVolume', function () {
beforeEach(function () {
state = jasmine.initializePlayer();
volumeControl = state.videoVolumeControl;
});
it('volume is increased correctly', function () {
var button = $('.volume .control');
volumeControl.volume = 60;
state.el.trigger(jQuery.Event("keydown", {
keyCode: $.ui.keyCode.UP
}));
// adjust the volume
button.focus();
button.trigger(keyPressEvent(KEY.UP));
expect(volumeControl.volume).toEqual(80);
});
......@@ -206,16 +211,19 @@ describe('VideoVolumeControl', function () {
});
describe('decreaseVolume', function () {
beforeEach(function () {
state = jasmine.initializePlayer();
volumeControl = state.videoVolumeControl;
});
it('volume is decreased correctly', function () {
var button = $('.volume .control');
volumeControl.volume = 60;
state.el.trigger(jQuery.Event("keydown", {
keyCode: $.ui.keyCode.DOWN
}));
// adjust the volume
button.focus();
button.trigger(keyPressEvent(KEY.DOWN));
expect(volumeControl.volume).toEqual(40);
});
......@@ -274,21 +282,21 @@ describe('VideoVolumeControl', function () {
it('nothing happens if ALT+keyUp are pushed down', function () {
assertVolumeIsNotChanged({
keyCode: $.ui.keyCode.UP,
keyCode: KEY.UP,
altKey: true
});
});
it('nothing happens if SHIFT+keyUp are pushed down', function () {
assertVolumeIsNotChanged({
keyCode: $.ui.keyCode.UP,
keyCode: KEY.UP,
shiftKey: true
});
});
it('nothing happens if SHIFT+keyDown are pushed down', function () {
assertVolumeIsNotChanged({
keyCode: $.ui.keyCode.DOWN,
keyCode: KEY.DOWN,
shiftKey: true
});
});
......@@ -302,8 +310,8 @@ describe('VideoVolumeControl', function () {
it('nothing happens if ALT+ENTER are pushed down', function () {
var isMuted = volumeControl.getMuteStatus();
$('.volume > a').trigger(jQuery.Event("keydown", {
keyCode: $.ui.keyCode.ENTER,
$('.volume .control').trigger(jQuery.Event("keydown", {
keyCode: KEY.ENTER,
altKey: true
}));
expect(volumeControl.getMuteStatus()).toEqual(isMuted);
......
......@@ -2,10 +2,14 @@
'use strict';
define('video/04_video_full_screen.js', [], function () {
var template = [
'<a href="#" class="add-fullscreen" title="',
gettext('Fill browser'), '" role="button" aria-disabled="false">',
'<button class="control add-fullscreen" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-arrows-alt" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Fill browser'),
'</a>'
'</span>',
'</span>',
'</button>'
].join('');
// VideoControl() function - what this module "exports".
......@@ -133,8 +137,12 @@ define('video/04_video_full_screen.js', [], function () {
fullScreenClassNameEl.removeClass('video-fullscreen');
$(window).scrollTop(this.scrollPos);
this.videoFullScreen.fullScreenEl
.attr('title', gettext('Fill browser'))
.find('.icon')
.removeClass('fa-compress')
.addClass('fa-arrows-alt')
.find('.control-text')
.text(gettext('Fill browser'));
this.el.trigger('fullscreen', [this.isFullScreen]);
}
......@@ -146,8 +154,12 @@ define('video/04_video_full_screen.js', [], function () {
this.videoFullScreen.fullScreenState = this.isFullScreen = true;
fullScreenClassNameEl.addClass('video-fullscreen');
this.videoFullScreen.fullScreenEl
.attr('title', gettext('Exit full browser'))
.find('.icon')
.removeClass('fa-arrows-alt')
.addClass('fa-compress')
.find('.control-text')
.text(gettext('Exit full browser'));
this.el.trigger('fullscreen', [this.isFullScreen]);
}
......
(function (requirejs, require, define) {
// VideoQualityControl module.
'use strict';
define(
'video/05_video_quality_control.js',
[],
function () {
var template = [
'<a href="#" class="quality-control is-hidden" title="',
gettext('HD off'), '" role="button" aria-disabled="false">',
gettext('HD off'),
'</a>'
'<button class="control quality-control is-hidden" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon icon-hd" aria-hidden="true">HD</span>', // "HD" is treated as a proper noun
// Translator note:
// HD stands for high definition
'<span class="sr text-translation">',
gettext('High Definition'),
'</span>&nbsp;',
'<span class="text control-text">',
// Translator note:
// Values are 'off' or 'on' depending on the state of the HD control
gettext('off'),
'</span>',
'</span>',
'</button>'
].join('');
// VideoQualityControl() function - what this module "exports".
......@@ -134,16 +146,16 @@ function () {
var controlStateStr;
this.videoQualityControl.quality = value;
if (_.contains(this.config.availableHDQualities, value)) {
controlStateStr = gettext('HD on');
controlStateStr = gettext('on');
this.videoQualityControl.el
.addClass('active')
.attr('title', controlStateStr)
.find('.control-text')
.text(controlStateStr);
} else {
controlStateStr = gettext('HD off');
controlStateStr = gettext('off');
this.videoQualityControl.el
.removeClass('active')
.attr('title', controlStateStr)
.find('.control-text')
.text(controlStateStr);
}
......
......@@ -38,13 +38,25 @@ function() {
step: 20,
template: [
'<div class="volume">',
'<a href="#" role="button" aria-disabled="false" title="',
gettext('Volume'), '" aria-label="',
gettext('Click on this button to mute or unmute this video or press UP or DOWN buttons to increase or decrease volume level.'),
'"></a>',
'<div role="presentation" class="volume-slider-container">',
'<div class="volume-slider"></div>',
'<div class="volume" role="application">',
'<button class="control" aria-disabled="false" aria-label="',
gettext('Volume: Click on this button to mute or unmute this video or press UP or ' +
'DOWN buttons to increase or decrease volume level.'),
'" aria-expanded="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-volume-up" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Volume'),
'</span>',
'</span>',
'</button>',
'<div class="volume-slider-container" aria-hidden="true">',
'<div class="volume-slider" ',
'role="slider"',
'aria-orientation="vertical" ',
'aria-valuemin="0" ',
'aria-valuemax="100" ',
'aria-valuenow=""></div>',
'</div>',
'</div>'
].join(''),
......@@ -89,7 +101,7 @@ function() {
// Youtube iframe react on key buttons and has his own handlers.
// So, we disallow focusing on iframe.
this.state.el.find('iframe').attr('tabindex', -1);
this.button = this.el.children('a');
this.button = this.el.children('.control');
this.cookie = new CookieManager(this.min, this.max);
this.a11y = new Accessibility(
this.button, this.min, this.max, this.i18n
......@@ -128,18 +140,17 @@ function() {
/** Bind any necessary function callbacks to DOM events. */
bindHandlers: function() {
this.state.el.on({
'keydown': this.keyDownHandler,
'play.volume': _.once(this.updateVolumeSilently),
'volumechange': this.onVolumeChangeHandler
});
this.el.on({
this.state.el.find('.volume').on({
'mouseenter': this.openMenu,
'mouseleave': this.closeMenu
});
this.button.on({
'keydown': this.keyDownHandler,
'click': false,
'mousedown': this.toggleMuteHandler,
'keydown': this.keyDownButtonHandler,
'focus': this.openMenu,
'blur': this.closeMenu
});
......@@ -194,6 +205,8 @@ function() {
var volume = Math.min(this.getVolume() + this.step, this.max);
this.setVolume(volume, false, false);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/** Decreases current volume level using previously defined step. */
......@@ -201,11 +214,15 @@ function() {
var volume = Math.max(this.getVolume() - this.step, this.min);
this.setVolume(volume, false, false);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/** Updates volume slider view. */
updateSliderView: function (volume) {
this.volumeSlider.slider('value', volume);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/**
......@@ -223,6 +240,8 @@ function() {
volume = muteStatus ? 0 : this.storedVolume;
this.setVolume(volume, false, false);
this.el.find('.volume-slider')
.attr('aria-valuenow', volume);
},
/**
......@@ -241,6 +260,18 @@ function() {
var action = isMuted ? 'addClass' : 'removeClass';
this.el[action]('is-muted');
if (isMuted) {
this.el
.find('.control .icon')
.removeClass('fa-volume-up')
.addClass('fa-volume-off');
} else {
this.el
.find('.control .icon')
.removeClass('fa-volume-off')
.addClass('fa-volume-up');
}
},
/** Toggles the state of the volume button. */
......@@ -266,11 +297,13 @@ function() {
/** Opens volume menu. */
openMenu: function() {
this.el.addClass('is-opened');
this.button.attr('aria-expanded', 'true');
},
/** Closes speed menu. */
closeMenu: function() {
this.el.removeClass('is-opened');
this.button.attr('aria-expanded', 'false');
},
/**
......@@ -310,6 +343,17 @@ function() {
this.decreaseVolume();
return false;
case KEY.SPACE:
case KEY.ENTER:
// Shift + Enter keyboard shortcut might be used by
// screen readers. In this case, do nothing.
if (event.shiftKey) {
return true;
}
this.toggleMute();
return false;
}
return true;
......@@ -333,7 +377,6 @@ function() {
case KEY.ENTER:
case KEY.SPACE:
this.toggleMute();
return false;
}
......@@ -347,6 +390,8 @@ function() {
*/
onSlideHandler: function(event, ui) {
this.setVolume(ui.value, false, true);
this.el.find('.volume-slider')
.attr('aria-valuenow', ui.volume);
},
/**
......@@ -395,10 +440,8 @@ function() {
initialize: function() {
this.liveRegion = $('<div />', {
'class': 'sr video-live-region',
'role': 'status',
'aria-hidden': 'false',
'aria-live': 'polite',
'aria-atomic': 'false'
'aria-live': 'polite'
});
this.button.after(this.liveRegion);
......@@ -413,6 +456,9 @@ function() {
this.getVolumeDescription(volume),
this.i18n['Volume'] + '.'
].join(' '));
$(this.button).parent().find('.volume-slider')
.attr('aria-valuenow', volume);
},
/**
......
(function (requirejs, require, define) {
"use strict";
define(
'video/08_video_speed_control.js',
['video/00_iterator.js'],
function (Iterator) {
"use strict";
/**
* Video speed control module.
* @exports video/08_video_speed_control.js
......@@ -29,13 +29,23 @@ function (Iterator) {
SpeedControl.prototype = {
template: [
'<div class="speeds menu-container">',
'<a class="speed-button" href="#" title="',
gettext('Speeds'), '" role="button" aria-disabled="false">',
'<span class="label">', gettext('Speed'), '</span>',
'<div class="speeds menu-container" role="application">',
'<button class="control speed-button" aria-label="',
/* jshint maxlen:200 */
gettext('Speed: Press UP to enter the speed menu then use the UP and DOWN arrow keys to navigate the different speeds, then press ENTER to change to the selected speed.'),
'" aria-disabled="false" aria-expanded="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-caret-right" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Speed'),
'</span>',
'</span>',
'<span class="label" aria-hidden="true">',
gettext('Speed'),
'</span>',
'<span class="value"></span>',
'</a>',
'<ol class="video-speeds menu" role="menu"></ol>',
'</button>',
'<ol class="video-speeds menu"></ol>',
'</div>'
].join(''),
......@@ -88,16 +98,16 @@ function (Iterator) {
reversedSpeeds = speeds.concat().reverse(),
speedsList = $.map(reversedSpeeds, function (speed) {
return [
'<li data-speed="', speed, '" role="presentation">',
'<a class="speed-link" href="#" role="menuitem" tabindex="-1">',
'<li data-speed="', speed, '">',
'<button class="control speed-option" tabindex="-1">',
speed, 'x',
'</a>',
'</button>',
'</li>'
].join('');
});
speedsContainer.html(speedsList.join(''));
this.speedLinks = new Iterator(speedsContainer.find('.speed-link'));
this.speedLinks = new Iterator(speedsContainer.find('.speed-option'));
this.state.el.find('.secondary-controls').prepend(this.el);
},
......@@ -110,7 +120,7 @@ function (Iterator) {
this.el.on({
'mouseenter': this.mouseEnterHandler,
'mouseleave': this.mouseLeaveHandler,
'click': this.clickMenuHandler,
'click': this.openMenu,
'keydown': this.keyDownMenuHandler
});
......@@ -119,7 +129,7 @@ function (Iterator) {
this.speedsContainer.on({
click: this.clickLinkHandler,
keydown: this.keyDownLinkHandler
}, 'a.speed-link');
}, '.speed-option');
this.state.el.on({
'speed:set': this.onSetSpeed,
......@@ -169,7 +179,9 @@ function (Iterator) {
}
this.el.addClass('is-opened');
this.speedButton.attr('tabindex', -1);
this.speedButton
.attr('tabindex', -1)
.attr('aria-expanded', 'true');
},
/**
......@@ -183,7 +195,9 @@ function (Iterator) {
}
this.el.removeClass('is-opened');
this.speedButton.attr('tabindex', 0);
this.speedButton
.attr('tabindex', 0)
.attr('aria-expanded', 'false');
},
/**
......@@ -216,7 +230,7 @@ function (Iterator) {
* Click event handler for the menu.
* @param {jquery Event} event
*/
clickMenuHandler: function (event) {
clickMenuHandler: function () {
this.closeMenu();
return false;
......@@ -239,7 +253,7 @@ function (Iterator) {
* Mouseenter event handler for the menu.
* @param {jquery Event} event
*/
mouseEnterHandler: function (event) {
mouseEnterHandler: function () {
this.openMenu();
return false;
......@@ -249,7 +263,7 @@ function (Iterator) {
* Mouseleave event handler for the menu.
* @param {jquery Event} event
*/
mouseLeaveHandler: function (event) {
mouseLeaveHandler: function () {
// Only close the menu is no speed entry has focus.
if (!this.speedLinks.list.is(':focus')) {
this.closeMenu();
......
......@@ -25,10 +25,14 @@ define('video/09_play_pause_control.js', [], function() {
PlayPauseControl.prototype = {
template: [
'<a class="video_control play" href="#" title="',
gettext('Play'), '" role="button" aria-disabled="false">',
'<button class="control video_control play" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-play" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Play'),
'</a>'
'</span>',
'</span>',
'</button>'
].join(''),
destroy: function () {
......@@ -71,14 +75,28 @@ define('video/09_play_pause_control.js', [], function() {
play: function () {
this.el
.attr('title', this.i18n['Pause']).text(this.i18n['Pause'])
.removeClass('play').addClass('pause');
.addClass('pause')
.removeClass('play')
.find('.icon')
.removeClass('fa-play')
.addClass('fa-pause');
this.el
.find('.control-text')
.text(gettext('Pause'));
},
pause: function () {
this.el
.attr('title', this.i18n['Play']).text(this.i18n['Play'])
.removeClass('pause').addClass('play');
.removeClass('pause')
.addClass('play')
.find('.icon')
.removeClass('fa-pause')
.addClass('fa-play');
this.el
.find('.control-text')
.text(gettext('Play'));
}
};
......
......@@ -25,10 +25,14 @@ define('video/09_play_skip_control.js', [], function() {
PlaySkipControl.prototype = {
template: [
'<a class="video_control play play-skip-control" href="#" title="',
gettext('Play'), '" role="button" aria-disabled="false">',
'<button class="control video_control play play-skip-control" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon icon-play" aria-hidden="true"></span>',
'<span class="text control-text">',
gettext('Play'),
'</a>'
'</span>',
'</span>',
'</button>'
].join(''),
destroy: function () {
......@@ -72,8 +76,13 @@ define('video/09_play_skip_control.js', [], function() {
play: function () {
this.el
.attr('title', gettext('Skip')).text(gettext('Skip'))
.removeClass('play').addClass('skip');
.removeClass('play')
.addClass('skip')
.find('.icon')
.removeClass('icon-play')
.addClass('icon-step-forward')
.find('.control-text')
.text(gettext('Skip'));
// Disable possibility to pause the video.
this.state.el.find('video').off('click');
}
......
......@@ -28,10 +28,14 @@ function() {
SkipControl.prototype = {
template: [
'<a class="video_control skip skip-control" href="#" title="',
gettext('Do not show again'), '" role="button" aria-disabled="false">',
'<button class="control video_control skip skip-control" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-step-forward" aria-hidden="true"></span>',
'<span class="text control-text">',
gettext('Do not show again'),
'</a>'
'</span>',
'</span>',
'</button>'
].join(''),
destroy: function () {
......@@ -51,7 +55,7 @@ function() {
* initial configuration.
*/
render: function() {
this.state.el.find('.vcr a').after(this.el);
this.state.el.find('.vcr .control').after(this.el);
},
/** Bind any necessary function callbacks to DOM events. */
......
(function (define) {
// VideoCaption module.
define(
'video/09_video_caption.js',
['video/00_sjson.js', 'video/00_async_process.js'],
function (Sjson, AsyncProcess) {
// VideoCaption module.
'use strict';
define(
'video/09_video_caption.js',
['video/00_sjson.js', 'video/00_async_process.js'],
function (Sjson, AsyncProcess) {
/**
* @desc VideoCaption module exports a function.
*
......@@ -25,7 +27,9 @@ function (Sjson, AsyncProcess) {
_.bindAll(this, 'toggle', 'onMouseEnter', 'onMouseLeave', 'onMovement',
'onContainerMouseEnter', 'onContainerMouseLeave', 'fetchCaption',
'onResize', 'pause', 'play', 'onCaptionUpdate', 'onCaptionHandler', 'destroy'
'onResize', 'pause', 'play', 'onCaptionUpdate', 'onCaptionHandler', 'destroy',
'handleKeypress', 'handleKeypressLink', 'openLanguageMenu', 'closeLanguageMenu',
'previousLanguageMenuItem', 'nextLanguageMenuItem'
);
this.state = state;
this.state.videoCaption = this;
......@@ -36,16 +40,34 @@ function (Sjson, AsyncProcess) {
VideoCaption.prototype = {
langTemplate: [
'<div class="lang menu-container">',
'<a href="#" class="hide-subtitles" title="',
gettext('Turn off captions'), '" role="button" aria-disabled="false">',
gettext('Turn off captions'),
'</a>',
'<div class="grouped-controls">',
'<button class="control toggle-transcript" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-quote-left" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Turn off transcript'),
'</span>',
'</span>',
'</button>',
'<div class="lang menu-container" role="application">',
'<button class="control language-menu" aria-label="',
/* jshint maxlen:250 */
gettext('Language: Press the UP arrow key to enter the language menu, then use UP and DOWN arrow keys to navigate language options. Press ENTER to change to the selected language.'),
'" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-caret-left" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Open language menu'),
'</span>',
'</span>',
'</button>',
'</div>',
'</div>'
].join(''),
template: [
'<ol id="transcript-captions" class="subtitles" tabindex="0" role="group" aria-label="',
'<ol id="transcript-captions" class="subtitles" aria-label="',
/* jshint maxlen:200 */
gettext('Activating an item in this group will spool the video to the corresponding time point. To skip transcript, go to previous item.'),
'">',
'<li></li>',
......@@ -85,7 +107,9 @@ function (Sjson, AsyncProcess) {
this.loaded = false;
this.subtitlesEl = $(this.template);
this.container = $(this.langTemplate);
this.hideSubtitlesEl = this.container.find('a.hide-subtitles');
this.transcriptControlEl = this.container.find('.toggle-transcript');
this.languageChooserEl = this.container.find('.lang');
this.menuChooserEl = this.languageChooserEl.parent();
if (_.keys(languages).length) {
this.renderLanguageMenu(languages);
......@@ -105,7 +129,7 @@ function (Sjson, AsyncProcess) {
'keydown'
].join(' ');
this.hideSubtitlesEl.on('click', this.toggle);
this.transcriptControlEl.on('click', this.toggle);
this.subtitlesEl
.on({
mouseenter: this.onMouseEnter,
......@@ -117,6 +141,14 @@ function (Sjson, AsyncProcess) {
.on(events, 'li[data-index]', this.onCaptionHandler);
if (this.showLanguageMenu) {
this.languageChooserEl.on({
keydown: this.handleKeypress
}, '.language-menu');
this.languageChooserEl.on({
keydown: this.handleKeypressLink
}, '.control-lang');
this.container.on({
mouseenter: this.onContainerMouseEnter,
mouseleave: this.onContainerMouseLeave
......@@ -144,6 +176,136 @@ function (Sjson, AsyncProcess) {
this.updatePlayTime(time);
},
handleKeypressLink: function(event) {
var KEY = $.ui.keyCode,
keyCode = event.keyCode,
focused, index, total;
switch(keyCode) {
case KEY.UP:
event.preventDefault();
focused = $(':focus').parent();
index = this.languageChooserEl.find('li').index(focused);
total = this.languageChooserEl.find('li').size() - 1;
this.previousLanguageMenuItem(event, index, total);
break;
case KEY.DOWN:
event.preventDefault();
focused = $(':focus').parent();
index = this.languageChooserEl.find('li').index(focused);
total = this.languageChooserEl.find('li').size() - 1;
this.nextLanguageMenuItem(event, index, total);
break;
case KEY.ESCAPE:
this.closeLanguageMenu(event);
break;
case KEY.ENTER:
case KEY.SPACE:
return true;
}
},
handleKeypress: function(event) {
var KEY = $.ui.keyCode,
keyCode = event.keyCode;
switch(keyCode) {
// Handle keypresses
case KEY.ENTER:
case KEY.SPACE:
case KEY.UP:
event.preventDefault();
this.openLanguageMenu(event);
break;
case KEY.ESCAPE:
this.closeLanguageMenu(event);
break;
}
return event.keyCode === KEY.TAB;
},
nextLanguageMenuItem: function(event, index, total) {
event.preventDefault();
if (event.altKey || event.shiftKey) {
return true;
}
if (index === total) {
this.languageChooserEl
.find('.control-lang').first()
.focus();
} else {
this.languageChooserEl
.find('li:eq(' + index + ')')
.next()
.find('.control-lang')
.focus();
}
return false;
},
previousLanguageMenuItem: function(event, index) {
event.preventDefault();
if (event.altKey) {
return true;
}
if (event.shiftKey) {
return true;
}
if (index === 0) {
this.languageChooserEl
.find('.control-lang').last()
.focus();
} else {
this.languageChooserEl
.find('li:eq(' + index + ')')
.prev()
.find('.control-lang')
.focus();
}
return false;
},
openLanguageMenu: function(event) {
event.preventDefault();
var button = this.languageChooserEl,
menu = button.parent().find('.menu');
this.state.el.trigger('language_menu:show');
button
.addClass('is-opened');
menu
.find('.control-lang').last()
.focus();
},
closeLanguageMenu: function(event) {
event.preventDefault();
var button = this.languageChooserEl;
this.state.el.trigger('language_menu:hide');
button
.removeClass('is-opened')
.find('.language-menu')
.focus();
},
onCaptionHandler: function (event) {
switch (event.type) {
case 'mouseover':
......@@ -175,7 +337,7 @@ function (Sjson, AsyncProcess) {
*/
onContainerMouseEnter: function (event) {
event.preventDefault();
$(event.currentTarget).addClass('is-opened');
$(event.currentTarget).find('.lang').addClass('is-opened');
this.state.el.trigger('language_menu:show');
},
......@@ -186,7 +348,7 @@ function (Sjson, AsyncProcess) {
*/
onContainerMouseLeave: function (event) {
event.preventDefault();
$(event.currentTarget).removeClass('is-opened');
$(event.currentTarget).find('.lang').removeClass('is-opened');
this.state.el.trigger('language_menu:hide');
},
......@@ -195,7 +357,7 @@ function (Sjson, AsyncProcess) {
*
* @param {jquery Event} event
*/
onMouseEnter: function (event) {
onMouseEnter: function () {
if (this.frozen) {
clearTimeout(this.frozen);
}
......@@ -211,7 +373,7 @@ function (Sjson, AsyncProcess) {
*
* @param {jquery Event} event
*/
onMouseLeave: function (event) {
onMouseLeave: function () {
if (this.frozen) {
clearTimeout(this.frozen);
}
......@@ -228,7 +390,7 @@ function (Sjson, AsyncProcess) {
*
* @param {jquery Event} event
*/
onMovement: function (event) {
onMovement: function () {
this.onMouseEnter();
},
......@@ -364,7 +526,9 @@ function (Sjson, AsyncProcess) {
self.fetchCaption(true);
} else {
self.hideCaptions(true, false);
self.hideSubtitlesEl.hide();
self.state.el.find('.lang').hide();
self.state.el.find('.transcript-control').hide();
self.subtitlesEl.hide();
}
}
});
......@@ -400,9 +564,11 @@ function (Sjson, AsyncProcess) {
self.renderLanguageMenu(newLanguages);
}
},
error: function (jqXHR, textStatus, errorThrown) {
error: function () {
self.hideCaptions(true, false);
self.hideSubtitlesEl.hide();
self.state.el.find('.lang').hide();
self.state.el.find('.transcript-control').hide();
self.subtitlesEl.hide();
}
});
......@@ -439,6 +605,8 @@ function (Sjson, AsyncProcess) {
currentLang = state.getCurrentLanguage();
if (_.keys(languages).length < 2) {
// Remove the menu toggle button
self.container.find('.lang').remove();
return false;
}
......@@ -446,7 +614,7 @@ function (Sjson, AsyncProcess) {
$.each(languages, function(code, label) {
var li = $('<li data-lang-code="' + code + '" />'),
link = $('<a href="javascript:void(0);">' + label + '</a>');
link = $('<button class="control control-lang">' + label + '</button>');
if (currentLang === code) {
li.addClass('is-active');
......@@ -456,9 +624,9 @@ function (Sjson, AsyncProcess) {
menu.append(li);
});
this.container.append(menu);
this.languageChooserEl.append(menu);
menu.on('click', 'a', function (e) {
menu.on('click', '.control-lang', function (e) {
var el = $(e.currentTarget).parent(),
state = self.state,
langCode = el.data('lang-code');
......@@ -489,6 +657,7 @@ function (Sjson, AsyncProcess) {
buildCaptions: function (container, start, captions) {
var process = function(text, index) {
var liEl = $('<li>', {
'role': 'link',
'data-index': index,
'data-start': start[index],
'tabindex': 0
......@@ -833,7 +1002,7 @@ function (Sjson, AsyncProcess) {
},
/**
* @desc Shows/Hides captions on click `CC` button
* @desc Shows/Hides transcript on click `transcript` button
*
* @param {jquery Event} event
*
......@@ -857,7 +1026,7 @@ function (Sjson, AsyncProcess) {
*
*/
hideCaptions: function (hide_captions, update_cookie, trigger_event) {
var hideSubtitlesEl = this.hideSubtitlesEl,
var transcriptControlEl = this.transcriptControlEl,
state = this.state, text;
if (typeof update_cookie === 'undefined') {
......@@ -867,23 +1036,29 @@ function (Sjson, AsyncProcess) {
if (hide_captions) {
state.captionsHidden = true;
state.el.addClass('closed');
text = gettext('Turn on captions');
text = gettext('Turn on transcripts');
if (trigger_event) {
this.state.el.trigger('captions:hide');
}
transcriptControlEl
.removeClass('is-active')
.find('.control-text')
.text(gettext(text));
} else {
state.captionsHidden = false;
state.el.removeClass('closed');
this.scrollCaption();
text = gettext('Turn off captions');
text = gettext('Turn off transcripts');
if (trigger_event) {
this.state.el.trigger('captions:show');
}
}
hideSubtitlesEl
.attr('title', text)
transcriptControlEl
.addClass('is-active')
.find('.control-text')
.text(gettext(text));
}
if (state.resizer) {
if (state.isFullScreen) {
......@@ -944,6 +1119,6 @@ function (Sjson, AsyncProcess) {
};
return VideoCaption;
});
});
}(RequireJS.define));
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="14" viewBox="0 0 12 14">
<path fill="#f2f2f2" d="M10.023 4.227l-2.773 2.773 2.773 2.773 1.125-1.125q0.227-0.242 0.547-0.109 0.305 0.133 0.305 0.461v3.5q0 0.203-0.148 0.352t-0.352 0.148h-3.5q-0.328 0-0.461-0.312-0.133-0.305 0.109-0.539l1.125-1.125-2.773-2.773-2.773 2.773 1.125 1.125q0.242 0.234 0.109 0.539-0.133 0.312-0.461 0.312h-3.5q-0.203 0-0.352-0.148t-0.148-0.352v-3.5q0-0.328 0.312-0.461 0.305-0.133 0.539 0.109l1.125 1.125 2.773-2.773-2.773-2.773-1.125 1.125q-0.148 0.148-0.352 0.148-0.094 0-0.187-0.039-0.312-0.133-0.312-0.461v-3.5q0-0.203 0.148-0.352t0.352-0.148h3.5q0.328 0 0.461 0.312 0.133 0.305-0.109 0.539l-1.125 1.125 2.773 2.773 2.773-2.773-1.125-1.125q-0.242-0.234-0.109-0.539 0.133-0.312 0.461-0.312h3.5q0.203 0 0.352 0.148t0.148 0.352v3.5q0 0.328-0.305 0.461-0.102 0.039-0.195 0.039-0.203 0-0.352-0.148z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="14" viewBox="0 0 5 14">
<path fill="#ffffff" d="M5 3.5v7q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-3.5-3.5q-0.148-0.148-0.148-0.352t0.148-0.352l3.5-3.5q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="14" viewBox="0 0 5 14">
<path fill="#f2f2f2" d="M4.5 7q0 0.203-0.148 0.352l-3.5 3.5q-0.148 0.148-0.352 0.148t-0.352-0.148-0.148-0.352v-7q0-0.203 0.148-0.352t0.352-0.148 0.352 0.148l3.5 3.5q0.148 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="14" viewBox="0 0 8 14">
<path fill="#f2f2f2" d="M8 9.5q0 0.203-0.148 0.352t-0.352 0.148h-7q-0.203 0-0.352-0.148t-0.148-0.352 0.148-0.352l3.5-3.5q0.148-0.148 0.352-0.148t0.352 0.148l3.5 3.5q0.148 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="14" viewBox="0 0 16 14">
<path fill="#f2f2f2" d="M6.133 7.875h1.617q-0.109 1.234-0.77 1.941t-1.676 0.707q-1.266 0-1.988-0.906t-0.723-2.469q0-1.516 0.727-2.434t1.82-0.918q1.156 0 1.812 0.68t0.758 1.93h-1.586q-0.039-0.5-0.277-0.773t-0.637-0.273q-0.445 0-0.691 0.473t-0.246 1.387q0 0.375 0.039 0.656t0.141 0.543 0.312 0.402 0.516 0.141q0.742 0 0.852-1.086zM11.695 7.875h1.609q-0.109 1.234-0.766 1.941t-1.672 0.707q-1.266 0-1.988-0.906t-0.723-2.469q0-1.516 0.727-2.434t1.82-0.918q1.156 0 1.812 0.68t0.758 1.93h-1.594q-0.031-0.5-0.273-0.773t-0.633-0.273q-0.445 0-0.691 0.473t-0.246 1.387q0 0.375 0.039 0.656t0.141 0.543 0.309 0.402 0.512 0.141q0.383 0 0.598-0.297t0.262-0.789zM14.5 6.945q0-1.617-0.121-2.398t-0.473-1.258q-0.047-0.062-0.105-0.109t-0.168-0.117-0.125-0.086q-0.672-0.492-5.445-0.492-4.883 0-5.547 0.492-0.039 0.031-0.137 0.090t-0.164 0.109-0.113 0.113q-0.352 0.469-0.469 1.246t-0.117 2.41q0 1.625 0.117 2.402t0.469 1.254q0.047 0.062 0.117 0.117t0.16 0.109 0.137 0.094q0.344 0.258 1.871 0.383t3.676 0.125q4.766 0 5.445-0.508 0.039-0.031 0.133-0.086t0.16-0.109 0.105-0.125q0.359-0.469 0.477-1.242t0.117-2.414zM16 1v12h-16v-12h16z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="14" viewBox="0 0 12 14">
<path fill="#f2f2f2" d="M6 7.5v3.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-1.125-1.125-2.594 2.594q-0.078 0.078-0.18 0.078t-0.18-0.078l-0.891-0.891q-0.078-0.078-0.078-0.18t0.078-0.18l2.594-2.594-1.125-1.125q-0.148-0.148-0.148-0.352t0.148-0.352 0.352-0.148h3.5q0.203 0 0.352 0.148t0.148 0.352zM11.898 2.25q0 0.102-0.078 0.18l-2.594 2.594 1.125 1.125q0.148 0.148 0.148 0.352t-0.148 0.352-0.352 0.148h-3.5q-0.203 0-0.352-0.148t-0.148-0.352v-3.5q0-0.203 0.148-0.352t0.352-0.148 0.352 0.148l1.125 1.125 2.594-2.594q0.078-0.078 0.18-0.078t0.18 0.078l0.891 0.891q0.078 0.078 0.078 0.18z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14">
<path fill="#f2f2f2" d="M3 9.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-0.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h0.5q0.102 0 0.176 0.074t0.074 0.176zM3 7.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-0.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h0.5q0.102 0 0.176 0.074t0.074 0.176zM3 5.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-0.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h0.5q0.102 0 0.176 0.074t0.074 0.176zM12 9.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-7.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h7.5q0.102 0 0.176 0.074t0.074 0.176zM12 7.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-7.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h7.5q0.102 0 0.176 0.074t0.074 0.176zM12 5.25v0.5q0 0.102-0.074 0.176t-0.176 0.074h-7.5q-0.102 0-0.176-0.074t-0.074-0.176v-0.5q0-0.102 0.074-0.176t0.176-0.074h7.5q0.102 0 0.176 0.074t0.074 0.176zM13 10.75v-6.5q0-0.102-0.074-0.176t-0.176-0.074h-11.5q-0.102 0-0.176 0.074t-0.074 0.176v6.5q0 0.102 0.074 0.176t0.176 0.074h11.5q0.102 0 0.176-0.074t0.074-0.176zM14 2.25v8.5q0 0.516-0.367 0.883t-0.883 0.367h-11.5q-0.516 0-0.883-0.367t-0.367-0.883v-8.5q0-0.516 0.367-0.883t0.883-0.367h11.5q0.516 0 0.883 0.367t0.367 0.883z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="14" viewBox="0 0 12 14">
<path fill="#f2f2f2" d="M12 1.5v11q0 0.203-0.148 0.352t-0.352 0.148h-4q-0.203 0-0.352-0.148t-0.148-0.352v-11q0-0.203 0.148-0.352t0.352-0.148h4q0.203 0 0.352 0.148t0.148 0.352zM5 1.5v11q0 0.203-0.148 0.352t-0.352 0.148h-4q-0.203 0-0.352-0.148t-0.148-0.352v-11q0-0.203 0.148-0.352t0.352-0.148h4q0.203 0 0.352 0.148t0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="14" viewBox="0 0 11 14">
<path fill="#f2f2f2" d="M10.812 7.242l-10.375 5.766q-0.18 0.102-0.309 0.023t-0.129-0.281v-11.5q0-0.203 0.129-0.281t0.309 0.023l10.375 5.766q0.18 0.102 0.18 0.242t-0.18 0.242z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="14" viewBox="0 0 13 14">
<path fill="#f2f2f2" d="M6 7.5v3q0 0.625-0.438 1.062t-1.062 0.438h-3q-0.625 0-1.062-0.438t-0.438-1.062v-5.5q0-0.813 0.316-1.551t0.855-1.277 1.277-0.855 1.551-0.316h0.5q0.203 0 0.352 0.148t0.148 0.352v1q0 0.203-0.148 0.352t-0.352 0.148h-0.5q-0.828 0-1.414 0.586t-0.586 1.414v0.25q0 0.312 0.219 0.531t0.531 0.219h1.75q0.625 0 1.062 0.438t0.438 1.062zM13 7.5v3q0 0.625-0.438 1.062t-1.062 0.438h-3q-0.625 0-1.062-0.438t-0.438-1.062v-5.5q0-0.813 0.316-1.551t0.855-1.277 1.277-0.855 1.551-0.316h0.5q0.203 0 0.352 0.148t0.148 0.352v1q0 0.203-0.148 0.352t-0.352 0.148h-0.5q-0.828 0-1.414 0.586t-0.586 1.414v0.25q0 0.312 0.219 0.531t0.531 0.219h1.75q0.625 0 1.062 0.438t0.438 1.062z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="14" viewBox="0 0 8 14">
<path fill="#f2f2f2" d="M0.352 12.898q-0.148 0.148-0.25 0.102t-0.102-0.25v-11.5q0-0.203 0.102-0.25t0.25 0.102l5.547 5.547q0.062 0.062 0.102 0.148v-5.297q0-0.203 0.148-0.352t0.352-0.148h1q0.203 0 0.352 0.148t0.148 0.352v11q0 0.203-0.148 0.352t-0.352 0.148h-1q-0.203 0-0.352-0.148t-0.148-0.352v-5.297q-0.039 0.078-0.102 0.148z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9" height="14" viewBox="0 0 9 14">
<path fill="#f2f2f2" d="M6 2.75v8.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-2.602-2.602h-2.047q-0.203 0-0.352-0.148t-0.148-0.352v-3q0-0.203 0.148-0.352t0.352-0.148h2.047l2.602-2.602q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352zM9 7q0 0.594-0.332 1.105t-0.879 0.73q-0.078 0.039-0.195 0.039-0.203 0-0.352-0.145t-0.148-0.355q0-0.164 0.094-0.277t0.227-0.195 0.266-0.18 0.227-0.277 0.094-0.445-0.094-0.445-0.227-0.277-0.266-0.18-0.227-0.195-0.094-0.277q0-0.211 0.148-0.355t0.352-0.145q0.117 0 0.195 0.039 0.547 0.211 0.879 0.727t0.332 1.109z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6" height="14" viewBox="0 0 6 14">
<path fill="#f2f2f2" d="M6 2.75v8.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-2.602-2.602h-2.047q-0.203 0-0.352-0.148t-0.148-0.352v-3q0-0.203 0.148-0.352t0.352-0.148h2.047l2.602-2.602q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352z"></path>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="14" viewBox="0 0 13 14">
<path fill="#f2f2f2" d="M6 2.75v8.5q0 0.203-0.148 0.352t-0.352 0.148-0.352-0.148l-2.602-2.602h-2.047q-0.203 0-0.352-0.148t-0.148-0.352v-3q0-0.203 0.148-0.352t0.352-0.148h2.047l2.602-2.602q0.148-0.148 0.352-0.148t0.352 0.148 0.148 0.352zM9 7q0 0.594-0.332 1.105t-0.879 0.73q-0.078 0.039-0.195 0.039-0.203 0-0.352-0.145t-0.148-0.355q0-0.164 0.094-0.277t0.227-0.195 0.266-0.18 0.227-0.277 0.094-0.445-0.094-0.445-0.227-0.277-0.266-0.18-0.227-0.195-0.094-0.277q0-0.211 0.148-0.355t0.352-0.145q0.117 0 0.195 0.039 0.547 0.211 0.879 0.727t0.332 1.109zM11 7q0 1.195-0.664 2.207t-1.758 1.473q-0.102 0.039-0.195 0.039-0.211 0-0.359-0.148t-0.148-0.352q0-0.305 0.305-0.461 0.438-0.227 0.594-0.344 0.578-0.422 0.902-1.059t0.324-1.355-0.324-1.355-0.902-1.059q-0.156-0.117-0.594-0.344-0.305-0.156-0.305-0.461 0-0.203 0.148-0.352t0.352-0.148q0.102 0 0.203 0.039 1.094 0.461 1.758 1.473t0.664 2.207zM13 7q0 1.797-0.992 3.301t-2.641 2.215q-0.102 0.039-0.203 0.039-0.203 0-0.352-0.148t-0.148-0.352q0-0.281 0.305-0.461 0.055-0.031 0.176-0.082t0.176-0.082q0.359-0.195 0.641-0.398 0.961-0.711 1.5-1.773t0.539-2.258-0.539-2.258-1.5-1.773q-0.281-0.203-0.641-0.398-0.055-0.031-0.176-0.082t-0.176-0.082q-0.305-0.18-0.305-0.461 0-0.203 0.148-0.352t0.352-0.148q0.102 0 0.203 0.039 1.648 0.711 2.641 2.215t0.992 3.301z"></path>
</svg>
/*! afontgarde - v0.1.6 - 2015-03-13
* https://github.com/filamentgroup/a-font-garde
* Copyright (c) 2015 Filament Group c/o Zach Leatherman
* MIT License */
.icon-fallback-text .icon {
display: none;
}
/*
ADDED BY afontgarde.js:
Note: sure .FONT_NAME comes first for adjoining classes bug in IE7.
.FONT_NAME.supports-generatedcontent .icon-fallback-text .icon {
display: inline-block;
}*/
.icon-fallback-img .text,
.icon-fallback-glyph .text/*,
ADDED BY afontgarde.js:
Note: sure .FONT_NAME comes first for adjoining classes bug in IE7.
.FONT_NAME.supports-generatedcontent .icon-fallback-text .text*/ {
/* visually hide but accessible (h5bp.com) */
clip: rect(0 0 0 0);
overflow: hidden;
position: absolute;
height: 1px;
width: 1px;
}
/* Careful, don’t use adjoining classes here (IE7) */
.supports-no-generatedcontent .icon-fallback-glyph .text,
.supports-no-generatedcontent .icon-fallback-img .text {
clip: auto;
overflow: visible;
position: static;
height: auto;
width: auto;
}
/*
ADDED BY afontgarde.js:
.FONT_NAME .icon-fallback-glyph .icon:before {
// inherit for font-size, line-height was not working on IE8
font-size: 1em;
font-size: inherit;
line-height: 1;
line-height: inherit;
}*/
.icon-fallback-img .icon {
display: inline-block;
}
// Necessary for xblocks and xmodules, but works across the board
html:not('.afontgarde') .icon-fallback-img .icon:before {
content: "";
}
/* The img fallback version is not as reliable since it does not check to make sure the fontloaded font has loaded. If we did add the .fontloaded class, it would unnecessarily request the fallback image. */
.fontawesome .icon-fallback-img .icon {
background-image: none;
}
\ No newline at end of file
/*! afontgarde - v0.1.6 - 2015-03-13
* https://github.com/filamentgroup/a-font-garde
* Copyright (c) 2015 Filament Group c/o Zach Leatherman
* MIT License */
/*! fontfaceonload - v0.1.6 - 2015-03-13
* https://github.com/zachleat/fontfaceonload
* Copyright (c) 2015 Zach Leatherman (@zachleat)
* MIT License */
;(function( win, doc ) {
"use strict";
var TEST_STRING = 'AxmTYklsjo190QW',
SANS_SERIF_FONTS = 'sans-serif',
SERIF_FONTS = 'serif',
// lighter and bolder not supported
weightLookup = {
normal: '400',
bold: '700'
},
defaultOptions = {
tolerance: 2, // px
delay: 100,
glyphs: '',
success: function() {},
error: function() {},
timeout: 5000,
weight: '400', // normal
style: 'normal'
},
// See https://github.com/typekit/webfontloader/blob/master/src/core/fontruler.js#L41
style = [
'display:block',
'position:absolute',
'top:-999px',
'left:-999px',
'font-size:48px',
'width:auto',
'height:auto',
'line-height:normal',
'margin:0',
'padding:0',
'font-variant:normal',
'white-space:nowrap'
],
html = '<div style="%s">' + TEST_STRING + '</div>';
var FontFaceOnloadInstance = function() {
this.fontFamily = '';
this.appended = false;
this.serif = undefined;
this.sansSerif = undefined;
this.parent = undefined;
this.options = {};
};
FontFaceOnloadInstance.prototype.getMeasurements = function () {
return {
sansSerif: {
width: this.sansSerif.offsetWidth,
height: this.sansSerif.offsetHeight
},
serif: {
width: this.serif.offsetWidth,
height: this.serif.offsetHeight
}
};
};
FontFaceOnloadInstance.prototype.load = function () {
var startTime = new Date(),
that = this,
serif = that.serif,
sansSerif = that.sansSerif,
parent = that.parent,
appended = that.appended,
dimensions,
options = this.options,
ref = options.reference;
function getStyle( family ) {
return style
.concat( [ 'font-weight:' + options.weight, 'font-style:' + options.style ] )
.concat( "font-family:" + family )
.join( ";" );
}
var sansSerifHtml = html.replace( /\%s/, getStyle( SANS_SERIF_FONTS ) ),
serifHtml = html.replace( /\%s/, getStyle( SERIF_FONTS ) );
if( !parent ) {
parent = that.parent = doc.createElement( "div" );
}
parent.innerHTML = sansSerifHtml + serifHtml;
sansSerif = that.sansSerif = parent.firstChild;
serif = that.serif = sansSerif.nextSibling;
if( options.glyphs ) {
sansSerif.innerHTML += options.glyphs;
serif.innerHTML += options.glyphs;
}
function hasNewDimensions( dims, el, tolerance ) {
return Math.abs( dims.width - el.offsetWidth ) > tolerance ||
Math.abs( dims.height - el.offsetHeight ) > tolerance;
}
function isTimeout() {
return ( new Date() ).getTime() - startTime.getTime() > options.timeout;
}
(function checkDimensions() {
if( !ref ) {
ref = doc.body;
}
if( !appended && ref ) {
ref.appendChild( parent );
appended = that.appended = true;
dimensions = that.getMeasurements();
// Make sure we set the new font-family after we take our initial dimensions:
// handles the case where FontFaceOnload is called after the font has already
// loaded.
sansSerif.style.fontFamily = that.fontFamily + ', ' + SANS_SERIF_FONTS;
serif.style.fontFamily = that.fontFamily + ', ' + SERIF_FONTS;
}
if( appended && dimensions &&
( hasNewDimensions( dimensions.sansSerif, sansSerif, options.tolerance ) ||
hasNewDimensions( dimensions.serif, serif, options.tolerance ) ) ) {
options.success();
} else if( isTimeout() ) {
options.error();
} else {
if( !appended && "requestAnimationFrame" in window ) {
win.requestAnimationFrame( checkDimensions );
} else {
win.setTimeout( checkDimensions, options.delay );
}
}
})();
}; // end load()
FontFaceOnloadInstance.prototype.checkFontFaces = function( timeout ) {
var _t = this;
doc.fonts.forEach(function( font ) {
if( font.family.toLowerCase() === _t.fontFamily.toLowerCase() &&
( weightLookup[ font.weight ] || font.weight ) === ''+_t.options.weight &&
font.style === _t.options.style ) {
font.load().then(function() {
_t.options.success();
win.clearTimeout( timeout );
});
}
});
};
FontFaceOnloadInstance.prototype.init = function( fontFamily, options ) {
var timeout;
for( var j in defaultOptions ) {
if( !options.hasOwnProperty( j ) ) {
options[ j ] = defaultOptions[ j ];
}
}
this.options = options;
this.fontFamily = fontFamily;
// For some reason this was failing on afontgarde + icon fonts.
if( !options.glyphs && "fonts" in doc ) {
if( options.timeout ) {
timeout = win.setTimeout(function() {
options.error();
}, options.timeout );
}
this.checkFontFaces( timeout );
} else {
this.load();
}
};
var FontFaceOnload = function( fontFamily, options ) {
var instance = new FontFaceOnloadInstance();
instance.init(fontFamily, options);
return instance;
};
// intentional global
win.FontFaceOnload = FontFaceOnload;
})( this, this.document );
/*
* A Font Garde
*/
;(function( w ) {
var doc = w.document,
ref,
css = ['.FONT_NAME.supports-generatedcontent .icon-fallback-text .icon { display: inline-block; }',
'.FONT_NAME.supports-generatedcontent .icon-fallback-text .text { clip: rect(0 0 0 0); overflow: hidden; position: absolute; height: 1px; width: 1px; }',
'.FONT_NAME .icon-fallback-glyph .icon:before { font-size: 1em; font-size: inherit; line-height: 1; line-height: inherit; }'];
function addEvent( type, callback ) {
if( 'addEventListener' in w ) {
return w.addEventListener( type, callback, false );
} else if( 'attachEvent' in w ) {
return w.attachEvent( 'on' + type, callback );
}
}
// options can be a string of glyphs or an options object to pass into FontFaceOnload
AFontGarde = function( fontFamily, options ) {
var fontFamilyClassName = fontFamily.toLowerCase().replace( /\s/g, '' ),
executed = false;
function init() {
if( executed ) {
return;
}
executed = true;
if( typeof FontFaceOnload === 'undefined' ) {
throw 'FontFaceOnload is a prerequisite.';
}
if( !ref ) {
ref = doc.getElementsByTagName( 'script' )[ 0 ];
}
var style = doc.createElement( 'style' ),
cssContent = css.join( '\n' ).replace( /FONT_NAME/gi, fontFamilyClassName );
style.setAttribute( 'type', 'text/css' );
if( style.styleSheet ) {
style.styleSheet.cssText = cssContent;
} else {
style.appendChild( doc.createTextNode( cssContent ) );
}
ref.parentNode.insertBefore( style, ref );
var opts = {
timeout: 5000,
success: function() {
// If you’re using more than one icon font, change this classname (and in a-font-garde.css)
doc.documentElement.className += ' ' + fontFamilyClassName;
if( options && options.success ) {
options.success();
}
}
};
// These characters are a few of the glyphs from the font above */
if( typeof options === "string" ) {
opts.glyphs = options;
} else {
for( var j in options ) {
if( options.hasOwnProperty( j ) && j !== "success" ) {
opts[ j ] = options[ j ];
}
}
}
FontFaceOnload( fontFamily, opts );
}
// MIT credit: filamentgroup/shoestring
addEvent( "DOMContentLoaded", init );
addEvent( "readystatechange", init );
addEvent( "load", init );
if( doc.readyState === "complete" ){
init();
}
};
})( this );
\ No newline at end of file
AFontGarde('FontAwesome', {
glyphs: '&#61515;'
});
\ No newline at end of file
/* Modernizr 2.7.1 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-fontface-generatedcontent-cssclasses-teststyles-cssclassprefix:supports!
*/
;window.Modernizr=function(a,b,c){function w(a){j.cssText=a}function x(a,b){return w(prefixes.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.7.1",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l=":)",m={}.toString,n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.fontface=function(){var a;return t('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},n.generatedcontent=function(){var a;return t(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a};for(var B in n)v(n,B)&&(s=B.toLowerCase(),e[s]=n[B](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" supports-"+(b?"":"no-")+a),e[a]=b}return e},w(""),i=k=null,e._version=d,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" supports-js supports-"+q.join(" supports-"):""),e}(this,this.document);
\ No newline at end of file
......@@ -14,7 +14,8 @@ import logging
log = logging.getLogger('VideoPage')
VIDEO_BUTTONS = {
'CC': '.hide-subtitles',
'transcript': '.lang',
'transcript_button': '.toggle-transcript',
'volume': '.volume',
'play': '.video_control.play',
'pause': '.video_control.pause',
......@@ -32,12 +33,12 @@ CSS_CLASS_NAMES = {
'captions': '.subtitles',
'captions_text': '.subtitles > li',
'error_message': '.video .video-player h3',
'video_container': 'div.video',
'video_container': '.video',
'video_sources': '.video-player video source',
'video_spinner': '.video-wrapper .spinner',
'video_xmodule': '.xmodule_VideoModule',
'video_init': '.is-initialized',
'video_time': 'div.vidtime',
'video_time': '.vidtime',
'video_display_name': '.vert h2',
'captions_lang_list': '.langs-list li',
'video_speed': '.speeds .value',
......@@ -45,8 +46,8 @@ CSS_CLASS_NAMES = {
}
VIDEO_MODES = {
'html5': 'div.video video',
'youtube': 'div.video iframe'
'html5': '.video video',
'youtube': '.video iframe'
}
VIDEO_MENUS = {
......@@ -99,7 +100,7 @@ class VideoPage(PageObject):
video_player_buttons.append('play')
for button in video_player_buttons:
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button.title()))
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button))
def _is_finished_loading():
"""
......@@ -126,7 +127,7 @@ class VideoPage(PageObject):
video_player_buttons = ['do_not_show_again', 'skip_bumper', 'volume']
for button in video_player_buttons:
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button.title()))
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button))
@property
def is_poster_shown(self):
......@@ -316,13 +317,13 @@ class VideoPage(PageObject):
states = {True: 'Shown', False: 'Hidden'}
state = states[captions_new_state]
# Make sure that the CC button is there
EmptyPromise(lambda: self.is_button_shown('CC'),
"CC button is shown").fulfill()
# Make sure that the transcript button is there
EmptyPromise(lambda: self.is_button_shown('transcript_button'),
"transcript button is shown").fulfill()
# toggle captions visibility state if needed
if self.is_captions_visible() != captions_new_state:
self.click_player_button('CC')
self.click_player_button('transcript_button')
# Verify that captions state is toggled/changed
EmptyPromise(lambda: self.is_captions_visible() == captions_new_state,
......@@ -371,7 +372,7 @@ class VideoPage(PageObject):
hover = ActionChains(self.browser).move_to_element(element_to_hover_over)
hover.perform()
speed_selector = self.get_element_selector('li[data-speed="{speed}"] a'.format(speed=speed))
speed_selector = self.get_element_selector('li[data-speed="{speed}"] .control'.format(speed=speed))
self.q(css=speed_selector).first.click()
def verify_speed_changed(self, expected_speed):
......@@ -548,8 +549,8 @@ class VideoPage(PageObject):
"""
self.wait_for_ajax()
# mouse over to CC button
cc_button_selector = self.get_element_selector(VIDEO_BUTTONS["CC"])
# mouse over to transcript button
cc_button_selector = self.get_element_selector(VIDEO_BUTTONS["transcript"])
element_to_hover_over = self.q(css=cc_button_selector).results[0]
ActionChains(self.browser).move_to_element(element_to_hover_over).perform()
......
......@@ -267,11 +267,11 @@ class CMSVideoTest(CMSVideoBaseTest):
"""
self._create_course_unit(subtitles=True)
self.video.click_player_button('CC')
self.video.click_player_button('transcript_button')
self.assertFalse(self.video.is_captions_visible())
self.video.click_player_button('CC')
self.video.click_player_button('transcript_button')
self.assertTrue(self.video.is_captions_visible())
......
......@@ -254,7 +254,7 @@ class YouTubeVideoTest(VideoBaseTest):
Then the "CC" button is hidden
"""
self.navigate_to_video()
self.assertFalse(self.video.is_button_shown('CC'))
self.assertFalse(self.video.is_button_shown('transcript_button'))
def test_fullscreen_video_alignment_with_transcript_hidden(self):
"""
......@@ -351,8 +351,8 @@ class YouTubeVideoTest(VideoBaseTest):
# check if video aligned correctly with enabled transcript
self.assertTrue(self.video.is_aligned(True))
# click video button "CC"
self.video.click_player_button('CC')
# click video button "transcript"
self.video.click_player_button('transcript_button')
# check if video aligned correctly without enabled transcript
self.assertTrue(self.video.is_aligned(False))
......@@ -459,7 +459,7 @@ class YouTubeVideoTest(VideoBaseTest):
self.assertTrue(self.video.is_video_rendered('html5'))
# check if caption button is visible
self.assertTrue(self.video.is_button_shown('CC'))
self.assertTrue(self.video.is_button_shown('transcript_button'))
self._verify_caption_text('Welcome to edX.')
def test_download_transcript_button_works_correctly(self):
......
......@@ -1273,6 +1273,9 @@ main_vendor_js = base_vendor_js + [
'js/vendor/jquery-ui.min.js',
'js/vendor/jquery.qtip.min.js',
'js/vendor/jquery.ba-bbq.min.js',
'js/vendor/afontgarde/modernizr.fontface-generatedcontent.js',
'js/vendor/afontgarde/afontgarde.js',
'js/vendor/afontgarde/edx-icons.js',
]
# Common files used by both RequireJS code and non-RequireJS code
......@@ -1376,6 +1379,7 @@ credit_web_view_js = [
PIPELINE_CSS = {
'style-vendor': {
'source_filenames': [
'js/vendor/afontgarde/afontgarde.css',
'css/vendor/font-awesome.css',
'css/vendor/jquery.qtip.min.css',
'css/vendor/responsive-carousel/responsive-carousel.css',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment