wai-website-theme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/_data/lang.json +730 -0
- data/_data/techniques.yml +180 -0
- data/_data/wcag.yml +125 -0
- data/_includes/.DS_Store +0 -0
- data/_includes/body-class.html +1 -0
- data/_includes/box.html +10 -0
- data/_includes/excol.html +13 -0
- data/_includes/footer.html +40 -0
- data/_includes/head.html +23 -0
- data/_includes/header.html +59 -0
- data/_includes/icon.html +6 -0
- data/_includes/img.html +17 -0
- data/_includes/multilang-list-policy-links.html +29 -0
- data/_includes/multilang-list.html +35 -0
- data/_includes/multilang-policy-title.html +5 -0
- data/_includes/multilang-title-full.html +1 -0
- data/_includes/multilang-title.html +1 -0
- data/_includes/navlist.html +22 -0
- data/_includes/notes.html +2 -0
- data/_includes/prevnext.html +34 -0
- data/_includes/resources.html +19 -0
- data/_includes/sidenav.html +65 -0
- data/_includes/sidenote.html +14 -0
- data/_includes/toc.html +10 -0
- data/_includes/video-player.html +99 -0
- data/_layouts/default.html +26 -0
- data/_layouts/home.html +14 -0
- data/_layouts/news.html +21 -0
- data/_layouts/none.html +1 -0
- data/_layouts/policy.html +72 -0
- data/_layouts/sidenav.html +27 -0
- data/_layouts/sidenavsidebar.html +22 -0
- data/assets/ableplayer/.gitattributes +14 -0
- data/assets/ableplayer/.gitignore +7 -0
- data/assets/ableplayer/Gruntfile.js +105 -0
- data/assets/ableplayer/LICENSE +26 -0
- data/assets/ableplayer/README.md +656 -0
- data/assets/ableplayer/build/ableplayer.dist.js +12157 -0
- data/assets/ableplayer/build/ableplayer.js +12157 -0
- data/assets/ableplayer/build/ableplayer.min.css +2 -0
- data/assets/ableplayer/build/ableplayer.min.js +8 -0
- data/assets/ableplayer/button-icons/able-icons.svg +116 -0
- data/assets/ableplayer/button-icons/black/captions.png +0 -0
- data/assets/ableplayer/button-icons/black/chapters.png +0 -0
- data/assets/ableplayer/button-icons/black/close.png +0 -0
- data/assets/ableplayer/button-icons/black/descriptions.png +0 -0
- data/assets/ableplayer/button-icons/black/ellipsis.png +0 -0
- data/assets/ableplayer/button-icons/black/faster.png +0 -0
- data/assets/ableplayer/button-icons/black/forward.png +0 -0
- data/assets/ableplayer/button-icons/black/fullscreen-collapse.png +0 -0
- data/assets/ableplayer/button-icons/black/fullscreen-expand.png +0 -0
- data/assets/ableplayer/button-icons/black/help.png +0 -0
- data/assets/ableplayer/button-icons/black/next.png +0 -0
- data/assets/ableplayer/button-icons/black/pause.png +0 -0
- data/assets/ableplayer/button-icons/black/pipe.png +0 -0
- data/assets/ableplayer/button-icons/black/play.png +0 -0
- data/assets/ableplayer/button-icons/black/preferences.png +0 -0
- data/assets/ableplayer/button-icons/black/previous.png +0 -0
- data/assets/ableplayer/button-icons/black/rabbit.png +0 -0
- data/assets/ableplayer/button-icons/black/restart.png +0 -0
- data/assets/ableplayer/button-icons/black/rewind.png +0 -0
- data/assets/ableplayer/button-icons/black/sign.png +0 -0
- data/assets/ableplayer/button-icons/black/slower.png +0 -0
- data/assets/ableplayer/button-icons/black/stop.png +0 -0
- data/assets/ableplayer/button-icons/black/transcript.png +0 -0
- data/assets/ableplayer/button-icons/black/turtle.png +0 -0
- data/assets/ableplayer/button-icons/black/volume-loud.png +0 -0
- data/assets/ableplayer/button-icons/black/volume-medium.png +0 -0
- data/assets/ableplayer/button-icons/black/volume-mute.png +0 -0
- data/assets/ableplayer/button-icons/black/volume-soft.png +0 -0
- data/assets/ableplayer/button-icons/fonts/able.eot +0 -0
- data/assets/ableplayer/button-icons/fonts/able.svg +40 -0
- data/assets/ableplayer/button-icons/fonts/able.ttf +0 -0
- data/assets/ableplayer/button-icons/fonts/able.woff +0 -0
- data/assets/ableplayer/button-icons/white/captions.png +0 -0
- data/assets/ableplayer/button-icons/white/chapters.png +0 -0
- data/assets/ableplayer/button-icons/white/close.png +0 -0
- data/assets/ableplayer/button-icons/white/descriptions.png +0 -0
- data/assets/ableplayer/button-icons/white/ellipsis.png +0 -0
- data/assets/ableplayer/button-icons/white/faster.png +0 -0
- data/assets/ableplayer/button-icons/white/forward.png +0 -0
- data/assets/ableplayer/button-icons/white/fullscreen-collapse.png +0 -0
- data/assets/ableplayer/button-icons/white/fullscreen-expand.png +0 -0
- data/assets/ableplayer/button-icons/white/help.png +0 -0
- data/assets/ableplayer/button-icons/white/next.png +0 -0
- data/assets/ableplayer/button-icons/white/pause.png +0 -0
- data/assets/ableplayer/button-icons/white/pipe.png +0 -0
- data/assets/ableplayer/button-icons/white/play.png +0 -0
- data/assets/ableplayer/button-icons/white/preferences.png +0 -0
- data/assets/ableplayer/button-icons/white/previous.png +0 -0
- data/assets/ableplayer/button-icons/white/rabbit.png +0 -0
- data/assets/ableplayer/button-icons/white/restart.png +0 -0
- data/assets/ableplayer/button-icons/white/rewind.png +0 -0
- data/assets/ableplayer/button-icons/white/sign.png +0 -0
- data/assets/ableplayer/button-icons/white/slower.png +0 -0
- data/assets/ableplayer/button-icons/white/stop.png +0 -0
- data/assets/ableplayer/button-icons/white/transcript.png +0 -0
- data/assets/ableplayer/button-icons/white/turtle.png +0 -0
- data/assets/ableplayer/button-icons/white/volume-loud.png +0 -0
- data/assets/ableplayer/button-icons/white/volume-medium.png +0 -0
- data/assets/ableplayer/button-icons/white/volume-mute.png +0 -0
- data/assets/ableplayer/button-icons/white/volume-soft.png +0 -0
- data/assets/ableplayer/images/wingrip.png +0 -0
- data/assets/ableplayer/package.json +22 -0
- data/assets/ableplayer/scripts/JQuery.doWhen.js +113 -0
- data/assets/ableplayer/scripts/ableplayer-base.js +440 -0
- data/assets/ableplayer/scripts/browser.js +162 -0
- data/assets/ableplayer/scripts/buildplayer.js +1609 -0
- data/assets/ableplayer/scripts/caption.js +385 -0
- data/assets/ableplayer/scripts/chapters.js +242 -0
- data/assets/ableplayer/scripts/control.js +1514 -0
- data/assets/ableplayer/scripts/description.js +283 -0
- data/assets/ableplayer/scripts/dialog.js +147 -0
- data/assets/ableplayer/scripts/dragdrop.js +766 -0
- data/assets/ableplayer/scripts/event.js +595 -0
- data/assets/ableplayer/scripts/initialize.js +725 -0
- data/assets/ableplayer/scripts/langs.js +750 -0
- data/assets/ableplayer/scripts/metadata.js +134 -0
- data/assets/ableplayer/scripts/misc.js +72 -0
- data/assets/ableplayer/scripts/preference.js +909 -0
- data/assets/ableplayer/scripts/search.js +171 -0
- data/assets/ableplayer/scripts/sign.js +92 -0
- data/assets/ableplayer/scripts/slider.js +454 -0
- data/assets/ableplayer/scripts/track.js +296 -0
- data/assets/ableplayer/scripts/transcript.js +590 -0
- data/assets/ableplayer/scripts/translation.js +66 -0
- data/assets/ableplayer/scripts/volume.js +383 -0
- data/assets/ableplayer/scripts/webvtt.js +765 -0
- data/assets/ableplayer/scripts/youtube.js +471 -0
- data/assets/ableplayer/styles/ableplayer.css +1241 -0
- data/assets/ableplayer/thirdparty/js.cookie.js +145 -0
- data/assets/ableplayer/thirdparty/modernizr.custom.js +4 -0
- data/assets/ableplayer/translations/ca.js +1 -0
- data/assets/ableplayer/translations/de.js +1 -0
- data/assets/ableplayer/translations/en.js +305 -0
- data/assets/ableplayer/translations/es.js +305 -0
- data/assets/ableplayer/translations/fr.js +305 -0
- data/assets/ableplayer/translations/it.js +303 -0
- data/assets/ableplayer/translations/ja.js +305 -0
- data/assets/ableplayer/translations/nl.js +305 -0
- data/assets/css/style.css +4360 -0
- data/assets/css/style.css.map +1 -0
- data/assets/fonts/anonymouspro-bold.woff +0 -0
- data/assets/fonts/anonymouspro-bold.woff2 +0 -0
- data/assets/fonts/anonymouspro-bolditalic.woff +0 -0
- data/assets/fonts/anonymouspro-bolditalic.woff2 +0 -0
- data/assets/fonts/anonymouspro-italic.woff +0 -0
- data/assets/fonts/anonymouspro-italic.woff2 +0 -0
- data/assets/fonts/anonymouspro-regular.woff +0 -0
- data/assets/fonts/anonymouspro-regular.woff2 +0 -0
- data/assets/fonts/notosans-bold.woff +0 -0
- data/assets/fonts/notosans-bold.woff2 +0 -0
- data/assets/fonts/notosans-bolditalic.woff +0 -0
- data/assets/fonts/notosans-bolditalic.woff2 +0 -0
- data/assets/fonts/notosans-italic.woff +0 -0
- data/assets/fonts/notosans-italic.woff2 +0 -0
- data/assets/fonts/notosans-regular.woff +0 -0
- data/assets/fonts/notosans-regular.woff2 +0 -0
- data/assets/images/.DS_Store +0 -0
- data/assets/images/Shape.svg +10 -0
- data/assets/images/icon-related-content.svg +14 -0
- data/assets/images/icons.svg +126 -0
- data/assets/images/teaser-image@1x.jpg +0 -0
- data/assets/images/teaser-image@2x.jpg +0 -0
- data/assets/images/w3c.sketch +0 -0
- data/assets/images/w3c.svg +10 -0
- data/assets/scripts/jquery.min.js +4 -0
- data/assets/scripts/main.js +208 -0
- data/assets/scripts/svg4everybody.js +1 -0
- metadata +257 -0
|
@@ -0,0 +1,1609 @@
|
|
|
1
|
+
(function ($) {
|
|
2
|
+
|
|
3
|
+
AblePlayer.prototype.injectPlayerCode = function() {
|
|
4
|
+
|
|
5
|
+
// create and inject surrounding HTML structure
|
|
6
|
+
// If IOS:
|
|
7
|
+
// If video:
|
|
8
|
+
// IOS does not support any of the player's functionality
|
|
9
|
+
// - everything plays in its own player
|
|
10
|
+
// Therefore, AblePlayer is not loaded & all functionality is disabled
|
|
11
|
+
// (this all determined. If this is IOS && video, this function is never called)
|
|
12
|
+
// If audio:
|
|
13
|
+
// HTML cannot be injected as a *parent* of the <audio> element
|
|
14
|
+
// It is therefore injected *after* the <audio> element
|
|
15
|
+
// This is only a problem in IOS 6 and earlier,
|
|
16
|
+
// & is a known bug, fixed in IOS 7
|
|
17
|
+
|
|
18
|
+
var thisObj, vidcapContainer, prefsGroups, i;
|
|
19
|
+
thisObj = this;
|
|
20
|
+
|
|
21
|
+
// create three wrappers and wrap them around the media element. From inner to outer:
|
|
22
|
+
// $mediaContainer - contains the original media element
|
|
23
|
+
// $ableDiv - contains the media player and all its objects (e.g., captions, controls, descriptions)
|
|
24
|
+
// $ableWrapper - contains additional widgets (e.g., transcript window, sign window)
|
|
25
|
+
this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent();
|
|
26
|
+
this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent();
|
|
27
|
+
this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent();
|
|
28
|
+
if (this.player !== 'youtube') {
|
|
29
|
+
this.$ableWrapper.css({
|
|
30
|
+
'max-width': this.playerMaxWidth + 'px'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.injectOffscreenHeading();
|
|
35
|
+
|
|
36
|
+
// youtube adds its own big play button
|
|
37
|
+
// if (this.mediaType === 'video' && this.player !== 'youtube') {
|
|
38
|
+
if (this.mediaType === 'video') {
|
|
39
|
+
if (this.iconType == 'font' && this.player !== 'youtube') {
|
|
40
|
+
this.injectBigPlayButton();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// add container that captions or description will be appended to
|
|
44
|
+
// Note: new Jquery object must be assigned _after_ wrap, hence the temp vidcapContainer variable
|
|
45
|
+
vidcapContainer = $('<div>',{
|
|
46
|
+
'class' : 'able-vidcap-container'
|
|
47
|
+
});
|
|
48
|
+
this.$vidcapContainer = this.$mediaContainer.wrap(vidcapContainer).parent();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.injectPlayerControlArea();
|
|
52
|
+
this.injectTextDescriptionArea();
|
|
53
|
+
|
|
54
|
+
if (this.transcriptType) {
|
|
55
|
+
if (this.transcriptType === 'popup' || this.transcriptType === 'external') {
|
|
56
|
+
this.injectTranscriptArea();
|
|
57
|
+
}
|
|
58
|
+
else if (this.transcriptType === 'manual') {
|
|
59
|
+
this.setupManualTranscript();
|
|
60
|
+
}
|
|
61
|
+
this.addTranscriptAreaEvents();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.injectAlert();
|
|
65
|
+
this.injectPlaylist();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
AblePlayer.prototype.injectOffscreenHeading = function () {
|
|
69
|
+
// Add offscreen heading to the media container.
|
|
70
|
+
// The heading injected in $ableDiv is one level deeper than the closest parent heading
|
|
71
|
+
// as determined by getNextHeadingLevel()
|
|
72
|
+
var headingType;
|
|
73
|
+
this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6
|
|
74
|
+
headingType = 'h' + this.playerHeadingLevel.toString();
|
|
75
|
+
this.$headingDiv = $('<' + headingType + '>');
|
|
76
|
+
this.$ableDiv.prepend(this.$headingDiv);
|
|
77
|
+
this.$headingDiv.addClass('able-offscreen');
|
|
78
|
+
this.$headingDiv.text(this.tt.playerHeading);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
AblePlayer.prototype.injectBigPlayButton = function () {
|
|
82
|
+
|
|
83
|
+
this.$bigPlayButton = $('<button>', {
|
|
84
|
+
'class': 'able-big-play-button icon-play',
|
|
85
|
+
'aria-hidden': true,
|
|
86
|
+
'tabindex': -1
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
var thisObj = this;
|
|
90
|
+
this.$bigPlayButton.click(function () {
|
|
91
|
+
thisObj.handlePlay();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.$mediaContainer.append(this.$bigPlayButton);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
AblePlayer.prototype.injectPlayerControlArea = function () {
|
|
98
|
+
this.$playerDiv = $('<div>', {
|
|
99
|
+
'class' : 'able-player',
|
|
100
|
+
'role' : 'region',
|
|
101
|
+
'aria-label' : this.mediaType + ' player'
|
|
102
|
+
});
|
|
103
|
+
this.$playerDiv.addClass('able-'+this.mediaType);
|
|
104
|
+
|
|
105
|
+
// The default skin depends a bit on a Now Playing div
|
|
106
|
+
// so go ahead and add one
|
|
107
|
+
// However, it's only populated if this.showNowPlaying = true
|
|
108
|
+
this.$nowPlayingDiv = $('<div>',{
|
|
109
|
+
'class' : 'able-now-playing',
|
|
110
|
+
'aria-live' : 'assertive',
|
|
111
|
+
'aria-atomic': 'true'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.$controllerDiv = $('<div>',{
|
|
115
|
+
'class' : 'able-controller'
|
|
116
|
+
});
|
|
117
|
+
this.$controllerDiv.addClass('able-' + this.iconColor + '-controls');
|
|
118
|
+
|
|
119
|
+
this.$statusBarDiv = $('<div>',{
|
|
120
|
+
'class' : 'able-status-bar'
|
|
121
|
+
});
|
|
122
|
+
this.$timer = $('<span>',{
|
|
123
|
+
'class' : 'able-timer'
|
|
124
|
+
});
|
|
125
|
+
this.$elapsedTimeContainer = $('<span>',{
|
|
126
|
+
'class': 'able-elapsedTime',
|
|
127
|
+
text: '0:00'
|
|
128
|
+
});
|
|
129
|
+
this.$durationContainer = $('<span>',{
|
|
130
|
+
'class': 'able-duration'
|
|
131
|
+
});
|
|
132
|
+
this.$timer.append(this.$elapsedTimeContainer).append(this.$durationContainer);
|
|
133
|
+
|
|
134
|
+
this.$speed = $('<span>',{
|
|
135
|
+
'class' : 'able-speed',
|
|
136
|
+
'aria-live' : 'assertive'
|
|
137
|
+
}).text(this.tt.speed + ': 1x');
|
|
138
|
+
|
|
139
|
+
this.$status = $('<span>',{
|
|
140
|
+
'class' : 'able-status',
|
|
141
|
+
'aria-live' : 'polite'
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Put everything together.
|
|
145
|
+
this.$statusBarDiv.append(this.$timer, this.$speed, this.$status);
|
|
146
|
+
this.$playerDiv.append(this.$nowPlayingDiv, this.$controllerDiv, this.$statusBarDiv);
|
|
147
|
+
this.$ableDiv.append(this.$playerDiv);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
AblePlayer.prototype.injectTextDescriptionArea = function () {
|
|
151
|
+
|
|
152
|
+
// create a div for exposing description
|
|
153
|
+
// description will be exposed via role="alert" & announced by screen readers
|
|
154
|
+
this.$descDiv = $('<div>',{
|
|
155
|
+
'class': 'able-descriptions',
|
|
156
|
+
'aria-live': 'assertive',
|
|
157
|
+
'aria-atomic': 'true'
|
|
158
|
+
});
|
|
159
|
+
// Start off with description hidden.
|
|
160
|
+
// It will be exposed conditionally within description.js > initDescription()
|
|
161
|
+
this.$descDiv.hide();
|
|
162
|
+
this.$ableDiv.append(this.$descDiv);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
AblePlayer.prototype.getDefaultWidth = function(which) {
|
|
166
|
+
|
|
167
|
+
// return default width of resizable elements
|
|
168
|
+
// these values are somewhat arbitrary, but seem to result in good usability
|
|
169
|
+
// if users disagree, they can resize (and resposition) them
|
|
170
|
+
if (which === 'transcript') {
|
|
171
|
+
return 450;
|
|
172
|
+
}
|
|
173
|
+
else if (which === 'sign') {
|
|
174
|
+
return 400;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
AblePlayer.prototype.positionDraggableWindow = function (which, width) {
|
|
179
|
+
|
|
180
|
+
// which is either 'transcript' or 'sign'
|
|
181
|
+
|
|
182
|
+
var cookie, cookiePos, $window, dragged, windowPos, currentWindowPos, firstTime, zIndex;
|
|
183
|
+
|
|
184
|
+
cookie = this.getCookie();
|
|
185
|
+
if (which === 'transcript') {
|
|
186
|
+
$window = this.$transcriptArea;
|
|
187
|
+
if (typeof cookie.transcript !== 'undefined') {
|
|
188
|
+
cookiePos = cookie.transcript;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (which === 'sign') {
|
|
192
|
+
$window = this.$signWindow;
|
|
193
|
+
if (typeof cookie.transcript !== 'undefined') {
|
|
194
|
+
cookiePos = cookie.sign;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (typeof cookiePos !== 'undefined' && !($.isEmptyObject(cookiePos))) {
|
|
198
|
+
// position window using stored values from cookie
|
|
199
|
+
$window.css({
|
|
200
|
+
'position': cookiePos['position'],
|
|
201
|
+
'width': cookiePos['width'],
|
|
202
|
+
'z-index': cookiePos['zindex']
|
|
203
|
+
});
|
|
204
|
+
if (cookiePos['position'] === 'absolute') {
|
|
205
|
+
$window.css({
|
|
206
|
+
'top': cookiePos['top'],
|
|
207
|
+
'left': cookiePos['left']
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// since cookie is not page-specific, z-index needs may vary across different pages
|
|
211
|
+
this.updateZIndex(which);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// position window using default values
|
|
215
|
+
windowPos = this.getOptimumPosition(which, width);
|
|
216
|
+
if (typeof width === 'undefined') {
|
|
217
|
+
width = this.getDefaultWidth(which);
|
|
218
|
+
}
|
|
219
|
+
$window.css({
|
|
220
|
+
'position': windowPos[0],
|
|
221
|
+
'width': width,
|
|
222
|
+
'z-index': windowPos[3]
|
|
223
|
+
});
|
|
224
|
+
if (windowPos[0] === 'absolute') {
|
|
225
|
+
$window.css({
|
|
226
|
+
'top': windowPos[1] + 'px',
|
|
227
|
+
'left': windowPos[2] + 'px',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
AblePlayer.prototype.getOptimumPosition = function (targetWindow, targetWidth) {
|
|
234
|
+
|
|
235
|
+
// returns optimum position for targetWindow, as an array with the following structure:
|
|
236
|
+
// 0 - CSS position ('absolute' or 'relative')
|
|
237
|
+
// 1 - top
|
|
238
|
+
// 2 - left
|
|
239
|
+
// 3 - zindex (if not default)
|
|
240
|
+
// targetWindow is either 'transcript' or 'sign'
|
|
241
|
+
// if there is room to the right of the player, position element there
|
|
242
|
+
// else if there is room the left of the player, position element there
|
|
243
|
+
// else position element beneath player
|
|
244
|
+
|
|
245
|
+
var gap, position, ableWidth, ableHeight, ableOffset, ableTop, ableLeft,
|
|
246
|
+
windowWidth, otherWindowWidth, zIndex;
|
|
247
|
+
|
|
248
|
+
if (typeof targetWidth === 'undefined') {
|
|
249
|
+
targetWidth = this.getDefaultWidth(targetWindow);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
gap = 5; // number of pixels to preserve between Able Player objects
|
|
253
|
+
|
|
254
|
+
position = []; // position, top, left
|
|
255
|
+
|
|
256
|
+
ableWidth = this.$ableDiv.width();
|
|
257
|
+
ableHeight = this.$ableDiv.height();
|
|
258
|
+
ableOffset = this.$ableDiv.offset();
|
|
259
|
+
ableTop = ableOffset.top;
|
|
260
|
+
ableLeft = ableOffset.left;
|
|
261
|
+
windowWidth = $(window).width();
|
|
262
|
+
otherWindowWidth = 0; // width of other visiable draggable windows will be added to this
|
|
263
|
+
|
|
264
|
+
if (targetWindow === 'transcript') {
|
|
265
|
+
if (typeof this.$signWindow !== 'undefined') {
|
|
266
|
+
if (this.$signWindow.is(':visible')) {
|
|
267
|
+
otherWindowWidth = this.$signWindow.width() + gap;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else if (targetWindow === 'sign') {
|
|
272
|
+
if (typeof this.$transcriptArea !== 'undefined') {
|
|
273
|
+
if (this.$transcriptArea.is(':visible')) {
|
|
274
|
+
otherWindowWidth = this.$transcriptArea.width() + gap;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (targetWidth < (windowWidth - (ableLeft + ableWidth + gap + otherWindowWidth))) {
|
|
279
|
+
// there's room to the left of $ableDiv
|
|
280
|
+
position[0] = 'absolute';
|
|
281
|
+
position[1] = 0;
|
|
282
|
+
position[2] = ableWidth + otherWindowWidth + gap;
|
|
283
|
+
}
|
|
284
|
+
else if (targetWidth + gap < ableLeft) {
|
|
285
|
+
// there's room to the right of $ableDiv
|
|
286
|
+
position[0] = 'absolute';
|
|
287
|
+
position[1] = 0;
|
|
288
|
+
position[2] = ableLeft - targetWidth - gap;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// position element below $ableDiv
|
|
292
|
+
position[0] = 'relative';
|
|
293
|
+
// no need to define top, left, or z-index
|
|
294
|
+
}
|
|
295
|
+
return position;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
AblePlayer.prototype.injectPoster = function ($element, context) {
|
|
299
|
+
|
|
300
|
+
// get poster attribute from media element and append that as an img to $element
|
|
301
|
+
// context is either 'youtube' or 'fallback'
|
|
302
|
+
var poster, width, height;
|
|
303
|
+
|
|
304
|
+
if (context === 'youtube') {
|
|
305
|
+
if (typeof this.ytWidth !== 'undefined') {
|
|
306
|
+
width = this.ytWidth;
|
|
307
|
+
height = this.ytHeight;
|
|
308
|
+
}
|
|
309
|
+
else if (typeof this.playerMaxWidth !== 'undefined') {
|
|
310
|
+
width = this.playerMaxWidth;
|
|
311
|
+
height = this.playerMaxHeight;
|
|
312
|
+
}
|
|
313
|
+
else if (typeof this.playerWidth !== 'undefined') {
|
|
314
|
+
width = this.playerWidth;
|
|
315
|
+
height = this.playerHeight;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else if (context === 'fallback') {
|
|
319
|
+
width = '100%';
|
|
320
|
+
height = 'auto';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (this.$media.attr('poster')) {
|
|
324
|
+
poster = this.$media.attr('poster');
|
|
325
|
+
this.$posterImg = $('<img>',{
|
|
326
|
+
'class': 'able-poster',
|
|
327
|
+
'src' : poster,
|
|
328
|
+
'alt' : "",
|
|
329
|
+
'role': "presentation",
|
|
330
|
+
'width': width,
|
|
331
|
+
'height': height
|
|
332
|
+
});
|
|
333
|
+
$element.append(this.$posterImg);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
AblePlayer.prototype.injectAlert = function () {
|
|
338
|
+
|
|
339
|
+
// inject two alerts, one visible for all users and one for screen reader users only
|
|
340
|
+
|
|
341
|
+
var top;
|
|
342
|
+
|
|
343
|
+
this.$alertBox = $('<div role="alert"></div>');
|
|
344
|
+
this.$alertBox.addClass('able-alert');
|
|
345
|
+
this.$alertBox.appendTo(this.$ableDiv);
|
|
346
|
+
if (this.mediaType == 'audio') {
|
|
347
|
+
top = -10;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
top = Math.round(this.$mediaContainer.offset().top * 10) / 10;
|
|
351
|
+
}
|
|
352
|
+
this.$alertBox.css({
|
|
353
|
+
top: top + 'px'
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
this.$srAlertBox = $('<div role="alert"></div>');
|
|
357
|
+
this.$srAlertBox.addClass('able-screenreader-alert');
|
|
358
|
+
this.$srAlertBox.appendTo(this.$ableDiv);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
AblePlayer.prototype.injectPlaylist = function () {
|
|
362
|
+
if (this.playlistEmbed === true) {
|
|
363
|
+
// move playlist into player, immediately before statusBarDiv
|
|
364
|
+
var playlistClone = this.$playlistDom.clone();
|
|
365
|
+
playlistClone.insertBefore(this.$statusBarDiv);
|
|
366
|
+
// Update to the new playlist copy.
|
|
367
|
+
this.$playlist = playlistClone.find('li');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (this.hasPlaylist && this.$sources.length === 0) {
|
|
371
|
+
// no source elements were provided. Construct them from the first playlist item
|
|
372
|
+
this.initializing = true;
|
|
373
|
+
this.swapSource(0);
|
|
374
|
+
// redefine this.$sources now that media contains one or more <source> elements
|
|
375
|
+
this.$sources = this.$media.find('source');
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// Create popup div and append to player
|
|
380
|
+
// 'which' parameter is either 'captions', 'chapters', 'prefs', or 'X-window' (e.g., "sign-window")
|
|
381
|
+
AblePlayer.prototype.createPopup = function (which) {
|
|
382
|
+
|
|
383
|
+
var thisObj, $popup, $thisButton, $thisListItem, $prevButton, $nextButton,
|
|
384
|
+
selectedTrackIndex, selectedTrack;
|
|
385
|
+
thisObj = this;
|
|
386
|
+
$popup = $('<div>',{
|
|
387
|
+
'id': this.mediaId + '-' + which + '-menu',
|
|
388
|
+
'class': 'able-popup'
|
|
389
|
+
});
|
|
390
|
+
if (which === 'chapters' || which === 'prefs' || which === 'sign-window' || which === 'transcript-window') {
|
|
391
|
+
$popup.addClass('able-popup-no-radio');
|
|
392
|
+
}
|
|
393
|
+
$popup.on('keydown',function (e) {
|
|
394
|
+
$thisButton = $(this).find('input:focus');
|
|
395
|
+
$thisListItem = $thisButton.parent();
|
|
396
|
+
if ($thisListItem.is(':first-child')) {
|
|
397
|
+
// this is the first button
|
|
398
|
+
$prevButton = $(this).find('input').last(); // wrap to bottom
|
|
399
|
+
$nextButton = $thisListItem.next().find('input');
|
|
400
|
+
}
|
|
401
|
+
else if ($thisListItem.is(':last-child')) {
|
|
402
|
+
// this is the last button
|
|
403
|
+
$prevButton = $thisListItem.prev().find('input');
|
|
404
|
+
$nextButton = $(this).find('input').first(); // wrap to top
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
$prevButton = $thisListItem.prev().find('input');
|
|
408
|
+
$nextButton = $thisListItem.next().find('input');
|
|
409
|
+
}
|
|
410
|
+
if (e.which === 9) { // Tab
|
|
411
|
+
if (e.shiftKey) {
|
|
412
|
+
$thisListItem.removeClass('able-focus');
|
|
413
|
+
$prevButton.focus();
|
|
414
|
+
$prevButton.parent().addClass('able-focus');
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
$thisListItem.removeClass('able-focus');
|
|
418
|
+
$nextButton.focus();
|
|
419
|
+
$nextButton.parent().addClass('able-focus');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else if (e.which === 40 || e.which === 39) { // down or right arrow
|
|
423
|
+
$thisListItem.removeClass('able-focus');
|
|
424
|
+
$nextButton.focus();
|
|
425
|
+
$nextButton.parent().addClass('able-focus');
|
|
426
|
+
}
|
|
427
|
+
else if (e.which == 38 || e.which === 37) { // up or left arrow
|
|
428
|
+
$thisListItem.removeClass('able-focus');
|
|
429
|
+
$prevButton.focus();
|
|
430
|
+
$prevButton.parent().addClass('able-focus');
|
|
431
|
+
}
|
|
432
|
+
else if (e.which === 32 || e.which === 13) { // space or enter
|
|
433
|
+
$('input:focus').click();
|
|
434
|
+
}
|
|
435
|
+
else if (e.which === 27) { // Escape
|
|
436
|
+
$thisListItem.removeClass('able-focus');
|
|
437
|
+
thisObj.closePopups();
|
|
438
|
+
}
|
|
439
|
+
e.preventDefault();
|
|
440
|
+
});
|
|
441
|
+
this.$controllerDiv.append($popup);
|
|
442
|
+
return $popup;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
AblePlayer.prototype.closePopups = function () {
|
|
446
|
+
if (this.chaptersPopup && this.chaptersPopup.is(':visible')) {
|
|
447
|
+
this.chaptersPopup.hide();
|
|
448
|
+
this.$chaptersButton.focus();
|
|
449
|
+
}
|
|
450
|
+
if (this.captionsPopup && this.captionsPopup.is(':visible')) {
|
|
451
|
+
this.captionsPopup.hide();
|
|
452
|
+
this.$ccButton.focus();
|
|
453
|
+
}
|
|
454
|
+
if (this.prefsPopup && this.prefsPopup.is(':visible')) {
|
|
455
|
+
this.prefsPopup.hide();
|
|
456
|
+
this.$prefsButton.focus();
|
|
457
|
+
}
|
|
458
|
+
if (this.$windowPopup && this.$windowPopup.is(':visible')) {
|
|
459
|
+
this.$windowPopup.hide();
|
|
460
|
+
this.$windowButton.show().focus();
|
|
461
|
+
}
|
|
462
|
+
if (this.$volumeSlider && this.$volumeSlider.is(':visible')) {
|
|
463
|
+
this.$volumeSlider.hide().attr('aria-hidden','true');
|
|
464
|
+
this.$volumeAlert.text(this.tt.volumeSliderClosed);
|
|
465
|
+
this.$volumeButton.focus();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
AblePlayer.prototype.setupPopups = function (which) {
|
|
470
|
+
|
|
471
|
+
// Create and fill in the popup menu forms for various controls.
|
|
472
|
+
// parameter 'which' is passed if refreshing content of an existing popup ('captions' or 'chapters')
|
|
473
|
+
|
|
474
|
+
var popups, thisObj, hasDefault, i, j,
|
|
475
|
+
tracks, trackList, trackItem, track,
|
|
476
|
+
radioName, radioId, trackButton, trackLabel,
|
|
477
|
+
prefCats, prefCat, prefLabel;
|
|
478
|
+
|
|
479
|
+
popups = [];
|
|
480
|
+
if (typeof which === 'undefined') {
|
|
481
|
+
popups.push('prefs');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (which === 'captions' || (typeof which === 'undefined')) {
|
|
485
|
+
if (typeof this.ytCaptions !== 'undefined') { // setup popup for YouTube captions
|
|
486
|
+
if (this.ytCaptions.length) {
|
|
487
|
+
popups.push('ytCaptions');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else { // setup popup for local captions
|
|
491
|
+
if (this.captions.length > 0) {
|
|
492
|
+
popups.push('captions');
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (which === 'chapters' || (typeof which === 'undefined')) {
|
|
497
|
+
if (this.chapters.length > 0 && this.useChaptersButton) {
|
|
498
|
+
popups.push('chapters');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (popups.length > 0) {
|
|
502
|
+
thisObj = this;
|
|
503
|
+
for (var i=0; i<popups.length; i++) {
|
|
504
|
+
var popup = popups[i];
|
|
505
|
+
hasDefault = false;
|
|
506
|
+
if (popup == 'prefs') {
|
|
507
|
+
this.prefsPopup = this.createPopup('prefs');
|
|
508
|
+
}
|
|
509
|
+
else if (popup == 'captions') {
|
|
510
|
+
if (typeof this.captionsPopup === 'undefined') {
|
|
511
|
+
this.captionsPopup = this.createPopup('captions');
|
|
512
|
+
}
|
|
513
|
+
tracks = this.captions;
|
|
514
|
+
}
|
|
515
|
+
else if (popup == 'chapters') {
|
|
516
|
+
if (typeof this.chaptersPopup === 'undefined') {
|
|
517
|
+
this.chaptersPopup = this.createPopup('chapters');
|
|
518
|
+
}
|
|
519
|
+
if (this.selectedChapters) {
|
|
520
|
+
tracks = this.selectedChapters.cues;
|
|
521
|
+
}
|
|
522
|
+
else if (this.chapters.length >= 1) {
|
|
523
|
+
tracks = this.chapters[0].cues;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
tracks = [];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else if (popup == 'ytCaptions') {
|
|
530
|
+
if (typeof this.captionsPopup === 'undefined') {
|
|
531
|
+
this.captionsPopup = this.createPopup('captions');
|
|
532
|
+
}
|
|
533
|
+
tracks = this.ytCaptions;
|
|
534
|
+
}
|
|
535
|
+
var trackList = $('<ul></ul>');
|
|
536
|
+
radioName = this.mediaId + '-' + popup + '-choice';
|
|
537
|
+
if (popup === 'prefs') {
|
|
538
|
+
prefCats = this.getPreferencesGroups();
|
|
539
|
+
for (j = 0; j < prefCats.length; j++) {
|
|
540
|
+
trackItem = $('<li></li>');
|
|
541
|
+
prefCat = prefCats[j];
|
|
542
|
+
if (prefCat === 'captions') {
|
|
543
|
+
prefLabel = this.tt.prefMenuCaptions;
|
|
544
|
+
}
|
|
545
|
+
else if (prefCat === 'descriptions') {
|
|
546
|
+
prefLabel = this.tt.prefMenuDescriptions;
|
|
547
|
+
}
|
|
548
|
+
else if (prefCat === 'keyboard') {
|
|
549
|
+
prefLabel = this.tt.prefMenuKeyboard;
|
|
550
|
+
}
|
|
551
|
+
else if (prefCat === 'transcript') {
|
|
552
|
+
prefLabel = this.tt.prefMenuTranscript;
|
|
553
|
+
}
|
|
554
|
+
radioId = this.mediaId + '-' + popup + '-' + j;
|
|
555
|
+
trackButton = $('<input>',{
|
|
556
|
+
'type': 'radio',
|
|
557
|
+
'val': prefCat,
|
|
558
|
+
'name': radioName,
|
|
559
|
+
'id': radioId
|
|
560
|
+
});
|
|
561
|
+
trackLabel = $('<label>',{
|
|
562
|
+
'for': radioId
|
|
563
|
+
});
|
|
564
|
+
trackLabel.text(prefLabel);
|
|
565
|
+
trackButton.click(function(event) {
|
|
566
|
+
var whichPref = $(this).attr('value');
|
|
567
|
+
thisObj.setFullscreen(false);
|
|
568
|
+
if (whichPref === 'captions') {
|
|
569
|
+
thisObj.captionPrefsDialog.show();
|
|
570
|
+
}
|
|
571
|
+
else if (whichPref === 'descriptions') {
|
|
572
|
+
thisObj.descPrefsDialog.show();
|
|
573
|
+
}
|
|
574
|
+
else if (whichPref === 'keyboard') {
|
|
575
|
+
thisObj.keyboardPrefsDialog.show();
|
|
576
|
+
}
|
|
577
|
+
else if (whichPref === 'transcript') {
|
|
578
|
+
thisObj.transcriptPrefsDialog.show();
|
|
579
|
+
}
|
|
580
|
+
thisObj.closePopups();
|
|
581
|
+
});
|
|
582
|
+
trackItem.append(trackButton,trackLabel);
|
|
583
|
+
trackList.append(trackItem);
|
|
584
|
+
}
|
|
585
|
+
this.prefsPopup.append(trackList);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
for (j = 0; j < tracks.length; j++) {
|
|
589
|
+
trackItem = $('<li></li>');
|
|
590
|
+
track = tracks[j];
|
|
591
|
+
radioId = this.mediaId + '-' + popup + '-' + j;
|
|
592
|
+
trackButton = $('<input>',{
|
|
593
|
+
'type': 'radio',
|
|
594
|
+
'val': j,
|
|
595
|
+
'name': radioName,
|
|
596
|
+
'id': radioId
|
|
597
|
+
});
|
|
598
|
+
if (track.def) {
|
|
599
|
+
trackButton.prop('checked',true);
|
|
600
|
+
hasDefault = true;
|
|
601
|
+
}
|
|
602
|
+
trackLabel = $('<label>',{
|
|
603
|
+
'for': radioId
|
|
604
|
+
});
|
|
605
|
+
if (track.language !== 'undefined') {
|
|
606
|
+
trackButton.attr('lang',track.language);
|
|
607
|
+
}
|
|
608
|
+
if (popup == 'captions' || popup == 'ytCaptions') {
|
|
609
|
+
trackLabel.text(track.label || track.language);
|
|
610
|
+
trackButton.click(this.getCaptionClickFunction(track));
|
|
611
|
+
}
|
|
612
|
+
else if (popup == 'chapters') {
|
|
613
|
+
trackLabel.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start));
|
|
614
|
+
var getClickFunction = function (time) {
|
|
615
|
+
return function () {
|
|
616
|
+
thisObj.seekTo(time);
|
|
617
|
+
// stopgap to prevent spacebar in Firefox from reopening popup
|
|
618
|
+
// immediately after closing it (used in handleChapters())
|
|
619
|
+
thisObj.hidingPopup = true;
|
|
620
|
+
thisObj.chaptersPopup.hide();
|
|
621
|
+
// Ensure stopgap gets cancelled if handleChapters() isn't called
|
|
622
|
+
// e.g., if user triggered button with Enter or mouse click, not spacebar
|
|
623
|
+
setTimeout(function() {
|
|
624
|
+
thisObj.hidingPopup = false;
|
|
625
|
+
}, 100);
|
|
626
|
+
thisObj.$chaptersButton.focus();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
trackButton.on('click keypress',getClickFunction(track.start));
|
|
630
|
+
}
|
|
631
|
+
trackItem.append(trackButton,trackLabel);
|
|
632
|
+
trackList.append(trackItem);
|
|
633
|
+
}
|
|
634
|
+
if (popup == 'captions' || popup == 'ytCaptions') {
|
|
635
|
+
// add a captions off button
|
|
636
|
+
radioId = this.mediaId + '-captions-off';
|
|
637
|
+
trackItem = $('<li></li>');
|
|
638
|
+
trackButton = $('<input>',{
|
|
639
|
+
'type': 'radio',
|
|
640
|
+
'name': radioName,
|
|
641
|
+
'id': radioId
|
|
642
|
+
});
|
|
643
|
+
trackLabel = $('<label>',{
|
|
644
|
+
'for': radioId
|
|
645
|
+
});
|
|
646
|
+
trackLabel.text(this.tt.captionsOff);
|
|
647
|
+
if (this.prefCaptions === 0) {
|
|
648
|
+
trackButton.prop('checked',true);
|
|
649
|
+
}
|
|
650
|
+
trackButton.click(this.getCaptionOffFunction());
|
|
651
|
+
trackItem.append(trackButton,trackLabel);
|
|
652
|
+
trackList.append(trackItem);
|
|
653
|
+
}
|
|
654
|
+
if (!hasDefault) { // no 'default' attribute was specified on any <track>
|
|
655
|
+
if ((popup == 'captions' || popup == 'ytCaptions') && (trackList.find('input:radio[lang=' + this.captionLang + ']'))) {
|
|
656
|
+
// check the button associated with the default caption language
|
|
657
|
+
// (as determined in control.js > syncTrackLanguages())
|
|
658
|
+
trackList.find('input:radio[lang=' + this.captionLang + ']').prop('checked',true);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
// check the first button
|
|
662
|
+
trackList.find('input').first().prop('checked',true);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (popup === 'captions' || popup === 'ytCaptions') {
|
|
666
|
+
this.captionsPopup.html(trackList);
|
|
667
|
+
}
|
|
668
|
+
else if (popup === 'chapters') {
|
|
669
|
+
this.chaptersPopup.html(trackList);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
AblePlayer.prototype.provideFallback = function(reason) {
|
|
677
|
+
|
|
678
|
+
// provide ultimate fallback for users who are unable to play the media
|
|
679
|
+
// reason is a specific error message
|
|
680
|
+
// if reason is 'NO SUPPORT', use standard text from translation file
|
|
681
|
+
|
|
682
|
+
var $fallbackDiv, width, mediaClone, fallback, fallbackText,
|
|
683
|
+
showBrowserList, browsers, i, b, browserList;
|
|
684
|
+
|
|
685
|
+
// Could show list of supporting browsers if 99.9% confident the error is truly an outdated browser
|
|
686
|
+
// Too many sites say "You need to update your browser" when in fact I'm using a current version
|
|
687
|
+
showBrowserList = false;
|
|
688
|
+
|
|
689
|
+
$fallbackDiv = $('<div>',{
|
|
690
|
+
'class' : 'able-fallback',
|
|
691
|
+
'role' : 'alert',
|
|
692
|
+
});
|
|
693
|
+
// override default width of .able-fallback with player width, if known
|
|
694
|
+
if (typeof this.playerMaxWidth !== 'undefined') {
|
|
695
|
+
width = this.playerMaxWidth + 'px';
|
|
696
|
+
}
|
|
697
|
+
else if (this.$media.attr('width')) {
|
|
698
|
+
width = parseInt(this.$media.attr('width'), 10) + 'px';
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
width = '100%';
|
|
702
|
+
}
|
|
703
|
+
$fallbackDiv.css('max-width',width);
|
|
704
|
+
|
|
705
|
+
// use fallback content that's nested inside the HTML5 media element, if there is any
|
|
706
|
+
mediaClone = this.$media.clone();
|
|
707
|
+
$('source, track', mediaClone).remove();
|
|
708
|
+
fallback = mediaClone.html().trim();
|
|
709
|
+
if (fallback.length) {
|
|
710
|
+
$fallbackDiv.html(fallback);
|
|
711
|
+
}
|
|
712
|
+
else if (reason == 'NO SUPPORT') {
|
|
713
|
+
// not using a supporting browser; use standard text from translation file
|
|
714
|
+
fallbackText = this.tt.fallbackError1 + ' ' + this.tt[this.mediaType] + '. ';
|
|
715
|
+
fallbackText += this.tt.fallbackError2 + ':';
|
|
716
|
+
fallback = $('<p>').text(fallbackText);
|
|
717
|
+
$fallbackDiv.html(fallback);
|
|
718
|
+
showBrowserList = true;
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
// show the reason
|
|
722
|
+
$fallbackDiv.text(reason);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (showBrowserList) {
|
|
726
|
+
browserList = $('<ul>');
|
|
727
|
+
browsers = this.getSupportingBrowsers();
|
|
728
|
+
for (i=0; i<browsers.length; i++) {
|
|
729
|
+
b = $('<li>');
|
|
730
|
+
b.text(browsers[i].name + ' ' + browsers[i].minVersion + ' ' + this.tt.orHigher);
|
|
731
|
+
browserList.append(b);
|
|
732
|
+
}
|
|
733
|
+
$fallbackDiv.append(browserList);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// if there's a poster, show that as well
|
|
737
|
+
this.injectPoster($fallbackDiv, 'fallback');
|
|
738
|
+
|
|
739
|
+
// inject $fallbackDiv into the DOM and remove broken content
|
|
740
|
+
if (typeof this.$ableWrapper !== 'undefined') {
|
|
741
|
+
this.$ableWrapper.before($fallbackDiv);
|
|
742
|
+
this.$ableWrapper.remove();
|
|
743
|
+
}
|
|
744
|
+
else if (typeof this.$media !== 'undefined') {
|
|
745
|
+
this.$media.before($fallbackDiv);
|
|
746
|
+
this.$media.remove();
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
$('body').prepend($fallbackDiv);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
AblePlayer.prototype.getSupportingBrowsers = function() {
|
|
754
|
+
|
|
755
|
+
var browsers = [];
|
|
756
|
+
browsers[0] = {
|
|
757
|
+
name:'Chrome',
|
|
758
|
+
minVersion: '31'
|
|
759
|
+
};
|
|
760
|
+
browsers[1] = {
|
|
761
|
+
name:'Firefox',
|
|
762
|
+
minVersion: '34'
|
|
763
|
+
};
|
|
764
|
+
browsers[2] = {
|
|
765
|
+
name:'Internet Explorer',
|
|
766
|
+
minVersion: '10'
|
|
767
|
+
};
|
|
768
|
+
browsers[3] = {
|
|
769
|
+
name:'Opera',
|
|
770
|
+
minVersion: '26'
|
|
771
|
+
};
|
|
772
|
+
browsers[4] = {
|
|
773
|
+
name:'Safari for Mac OS X',
|
|
774
|
+
minVersion: '7.1'
|
|
775
|
+
};
|
|
776
|
+
browsers[5] = {
|
|
777
|
+
name:'Safari for iOS',
|
|
778
|
+
minVersion: '7.1'
|
|
779
|
+
};
|
|
780
|
+
browsers[6] = {
|
|
781
|
+
name:'Android Browser',
|
|
782
|
+
minVersion: '4.1'
|
|
783
|
+
};
|
|
784
|
+
browsers[7] = {
|
|
785
|
+
name:'Chrome for Android',
|
|
786
|
+
minVersion: '40'
|
|
787
|
+
};
|
|
788
|
+
return browsers;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Calculates the layout for controls based on media and options.
|
|
792
|
+
// Returns an object with keys 'ul', 'ur', 'bl', 'br' for upper-left, etc.
|
|
793
|
+
// Each associated value is array of control names to put at that location.
|
|
794
|
+
AblePlayer.prototype.calculateControlLayout = function () {
|
|
795
|
+
// Removed rewind/forward in favor of seek bar.
|
|
796
|
+
|
|
797
|
+
var controlLayout = {
|
|
798
|
+
'ul': ['play','restart','rewind','forward'],
|
|
799
|
+
'ur': ['seek'],
|
|
800
|
+
'bl': [],
|
|
801
|
+
'br': []
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// test for browser support for volume before displaying volume button
|
|
805
|
+
if (this.browserSupportsVolume()) {
|
|
806
|
+
// volume buttons are: 'mute','volume-soft','volume-medium','volume-loud'
|
|
807
|
+
// previously supported button were: 'volume-up','volume-down'
|
|
808
|
+
this.volumeButton = 'volume-' + this.getVolumeName(this.volume);
|
|
809
|
+
controlLayout['ur'].push('volume');
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
this.volume = false;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Calculate the two sides of the bottom-left grouping to see if we need separator pipe.
|
|
816
|
+
var bll = [];
|
|
817
|
+
var blr = [];
|
|
818
|
+
|
|
819
|
+
if (this.isPlaybackRateSupported()) {
|
|
820
|
+
bll.push('slower');
|
|
821
|
+
bll.push('faster');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (this.mediaType === 'video') {
|
|
825
|
+
if (this.hasCaptions) {
|
|
826
|
+
bll.push('captions'); //closed captions
|
|
827
|
+
}
|
|
828
|
+
if (this.hasSignLanguage) {
|
|
829
|
+
bll.push('sign'); // sign language
|
|
830
|
+
}
|
|
831
|
+
if ((this.hasOpenDesc || this.hasClosedDesc) && (this.useDescriptionsButton)) {
|
|
832
|
+
bll.push('descriptions'); //audio description
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (this.transcriptType === 'popup') {
|
|
836
|
+
bll.push('transcript');
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (this.mediaType === 'video' && this.hasChapters && this.useChaptersButton) {
|
|
840
|
+
bll.push('chapters');
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
controlLayout['br'].push('preferences');
|
|
844
|
+
|
|
845
|
+
// TODO: JW currently has a bug with fullscreen, anything that can be done about this?
|
|
846
|
+
if (this.mediaType === 'video' && this.allowFullScreen && this.player !== 'jw') {
|
|
847
|
+
controlLayout['br'].push('fullscreen');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Include the pipe only if we need to.
|
|
851
|
+
if (bll.length > 0 && blr.length > 0) {
|
|
852
|
+
controlLayout['bl'] = bll;
|
|
853
|
+
controlLayout['bl'].push('pipe');
|
|
854
|
+
controlLayout['bl'] = controlLayout['bl'].concat(blr);
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
controlLayout['bl'] = bll.concat(blr);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return controlLayout;
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
AblePlayer.prototype.addControls = function() {
|
|
864
|
+
|
|
865
|
+
// determine which controls to show based on several factors:
|
|
866
|
+
// mediaType (audio vs video)
|
|
867
|
+
// availability of tracks (e.g., for closed captions & audio description)
|
|
868
|
+
// browser support (e.g., for sliders and speedButtons)
|
|
869
|
+
// user preferences (???)
|
|
870
|
+
// some controls are aligned on the left, and others on the right
|
|
871
|
+
var useSpeedButtons, useFullScreen,
|
|
872
|
+
i, j, k, controls, $controllerSpan, tooltipId, tooltipX, tooltipY, control,
|
|
873
|
+
buttonImg, buttonImgSrc, buttonTitle, newButton, iconClass, buttonIcon, buttonUse,
|
|
874
|
+
leftWidth, rightWidth, totalWidth, leftWidthStyle, rightWidthStyle,
|
|
875
|
+
controllerStyles, vidcapStyles, captionLabel, popupMenuId;
|
|
876
|
+
|
|
877
|
+
var thisObj = this;
|
|
878
|
+
|
|
879
|
+
var baseSliderWidth = 100;
|
|
880
|
+
|
|
881
|
+
// Initializes the layout into the this.controlLayout variable.
|
|
882
|
+
var controlLayout = this.calculateControlLayout();
|
|
883
|
+
|
|
884
|
+
var sectionByOrder = {0: 'ul', 1:'ur', 2:'bl', 3:'br'};
|
|
885
|
+
|
|
886
|
+
// add an empty div to serve as a tooltip
|
|
887
|
+
tooltipId = this.mediaId + '-tooltip';
|
|
888
|
+
this.$tooltipDiv = $('<div>',{
|
|
889
|
+
'id': tooltipId,
|
|
890
|
+
'class': 'able-tooltip'
|
|
891
|
+
});
|
|
892
|
+
this.$controllerDiv.append(this.$tooltipDiv);
|
|
893
|
+
|
|
894
|
+
// step separately through left and right controls
|
|
895
|
+
for (i = 0; i <= 3; i++) {
|
|
896
|
+
controls = controlLayout[sectionByOrder[i]];
|
|
897
|
+
if ((i % 2) === 0) {
|
|
898
|
+
$controllerSpan = $('<div>',{
|
|
899
|
+
'class': 'able-left-controls'
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
$controllerSpan = $('<div>',{
|
|
904
|
+
'class': 'able-right-controls'
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
this.$controllerDiv.append($controllerSpan);
|
|
908
|
+
for (j=0; j<controls.length; j++) {
|
|
909
|
+
control = controls[j];
|
|
910
|
+
if (control === 'seek') {
|
|
911
|
+
var sliderDiv = $('<div class="able-seekbar"></div>');
|
|
912
|
+
var sliderLabel = this.mediaType + ' ' + this.tt.seekbarLabel;
|
|
913
|
+
$controllerSpan.append(sliderDiv);
|
|
914
|
+
var duration = this.getDuration();
|
|
915
|
+
if (duration == 0) {
|
|
916
|
+
// set arbitrary starting duration, and change it when duration is known
|
|
917
|
+
duration = 100;
|
|
918
|
+
}
|
|
919
|
+
this.seekBar = new AccessibleSlider(this.mediaType, sliderDiv, 'horizontal', baseSliderWidth, 0, duration, this.seekInterval, sliderLabel, 'seekbar', true, 'visible');
|
|
920
|
+
}
|
|
921
|
+
else if (control === 'pipe') {
|
|
922
|
+
// TODO: Unify this with buttons somehow to avoid code duplication
|
|
923
|
+
var pipe = $('<span>', {
|
|
924
|
+
'tabindex': '-1',
|
|
925
|
+
'aria-hidden': 'true'
|
|
926
|
+
});
|
|
927
|
+
if (this.iconType === 'font') {
|
|
928
|
+
pipe.addClass('icon-pipe');
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
var pipeImg = $('<img>', {
|
|
932
|
+
src: this.rootPath + 'button-icons/' + this.iconColor + '/pipe.png',
|
|
933
|
+
alt: '',
|
|
934
|
+
role: 'presentation'
|
|
935
|
+
});
|
|
936
|
+
pipe.append(pipeImg);
|
|
937
|
+
}
|
|
938
|
+
$controllerSpan.append(pipe);
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
// this control is a button
|
|
942
|
+
if (control === 'volume') {
|
|
943
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/' + this.volumeButton + '.png';
|
|
944
|
+
}
|
|
945
|
+
else if (control === 'fullscreen') {
|
|
946
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/fullscreen-expand.png';
|
|
947
|
+
}
|
|
948
|
+
else if (control === 'slower') {
|
|
949
|
+
if (this.speedIcons === 'animals') {
|
|
950
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/turtle.png';
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/slower.png';
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
else if (control === 'faster') {
|
|
957
|
+
if (this.speedIcons === 'animals') {
|
|
958
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/rabbit.png';
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/faster.png';
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
buttonImgSrc = this.rootPath + 'button-icons/' + this.iconColor + '/' + control + '.png';
|
|
966
|
+
}
|
|
967
|
+
buttonTitle = this.getButtonTitle(control);
|
|
968
|
+
|
|
969
|
+
// icomoon documentation recommends the following markup for screen readers:
|
|
970
|
+
// 1. link element (or in our case, button). Nested inside this element:
|
|
971
|
+
// 2. span that contains the icon font (in our case, buttonIcon)
|
|
972
|
+
// 3. span that contains a visually hidden label for screen readers (buttonLabel)
|
|
973
|
+
// In addition, we are adding aria-label to the button (but not title)
|
|
974
|
+
// And if iconType === 'image', we are replacing #2 with an image (with alt="" and role="presentation")
|
|
975
|
+
// This has been thoroughly tested and works well in all screen reader/browser combinations
|
|
976
|
+
// See https://github.com/ableplayer/ableplayer/issues/81
|
|
977
|
+
newButton = $('<button>',{
|
|
978
|
+
'type': 'button',
|
|
979
|
+
'tabindex': '0',
|
|
980
|
+
'aria-label': buttonTitle,
|
|
981
|
+
'class': 'able-button-handler-' + control
|
|
982
|
+
});
|
|
983
|
+
if (control === 'volume' || control === 'preferences') {
|
|
984
|
+
// This same ARIA for captions and chapters are added elsewhere
|
|
985
|
+
if (control == 'preferences') {
|
|
986
|
+
popupMenuId = this.mediaId + '-prefs-menu';
|
|
987
|
+
}
|
|
988
|
+
else if (control === 'volume') {
|
|
989
|
+
popupMenuId = this.mediaId + '-volume-slider';
|
|
990
|
+
}
|
|
991
|
+
newButton.attr({
|
|
992
|
+
'aria-controls': popupMenuId
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (this.iconType === 'font') {
|
|
996
|
+
if (control === 'volume') {
|
|
997
|
+
iconClass = 'icon-' + this.volumeButton;
|
|
998
|
+
}
|
|
999
|
+
else if (control === 'slower') {
|
|
1000
|
+
if (this.speedIcons === 'animals') {
|
|
1001
|
+
iconClass = 'icon-turtle';
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
iconClass = 'icon-slower';
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
else if (control === 'faster') {
|
|
1008
|
+
if (this.speedIcons === 'animals') {
|
|
1009
|
+
iconClass = 'icon-rabbit';
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
iconClass = 'icon-faster';
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
iconClass = 'icon-' + control;
|
|
1017
|
+
}
|
|
1018
|
+
buttonIcon = $('<span>',{
|
|
1019
|
+
'class': iconClass,
|
|
1020
|
+
'aria-hidden': 'true'
|
|
1021
|
+
});
|
|
1022
|
+
newButton.append(buttonIcon);
|
|
1023
|
+
}
|
|
1024
|
+
else if (this.iconType === 'svg') {
|
|
1025
|
+
if (control === 'volume') {
|
|
1026
|
+
iconClass = 'svg-' + this.volumeButton;
|
|
1027
|
+
}
|
|
1028
|
+
else if (control === 'fullscreen') {
|
|
1029
|
+
iconClass = 'svg-fullscreen-expand';
|
|
1030
|
+
}
|
|
1031
|
+
else if (control === 'slower') {
|
|
1032
|
+
if (this.speedIcons === 'animals') {
|
|
1033
|
+
iconClass = 'svg-turtle';
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
iconClass = 'svg-slower';
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
else if (control === 'faster') {
|
|
1040
|
+
if (this.speedIcons === 'animals') {
|
|
1041
|
+
iconClass = 'svg-rabbit';
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
iconClass = 'svg-faster';
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
iconClass = 'svg-' + control;
|
|
1049
|
+
}
|
|
1050
|
+
buttonIcon = $('<svg>',{
|
|
1051
|
+
'class': iconClass
|
|
1052
|
+
});
|
|
1053
|
+
buttonUse = $('<use>',{
|
|
1054
|
+
'xlink:href': this.rootPath + 'icons/able-icons.svg#' + iconClass
|
|
1055
|
+
});
|
|
1056
|
+
buttonIcon.append(buttonUse);
|
|
1057
|
+
newButton.html(buttonIcon);
|
|
1058
|
+
|
|
1059
|
+
// Final step: Need to refresh the DOM in order for browser to process & display the SVG
|
|
1060
|
+
newButton.html(newButton.html());
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
// use images
|
|
1064
|
+
buttonImg = $('<img>',{
|
|
1065
|
+
'src': buttonImgSrc,
|
|
1066
|
+
'alt': '',
|
|
1067
|
+
'role': 'presentation'
|
|
1068
|
+
});
|
|
1069
|
+
newButton.append(buttonImg);
|
|
1070
|
+
}
|
|
1071
|
+
// add the visibly-hidden label for screen readers that don't support aria-label on the button
|
|
1072
|
+
var buttonLabel = $('<span>',{
|
|
1073
|
+
'class': 'able-clipped'
|
|
1074
|
+
}).text(buttonTitle);
|
|
1075
|
+
newButton.append(buttonLabel);
|
|
1076
|
+
// add an event listener that displays a tooltip on mouseenter or focus
|
|
1077
|
+
newButton.on('mouseenter focus',function(event) {
|
|
1078
|
+
var label = $(this).attr('aria-label');
|
|
1079
|
+
// get position of this button
|
|
1080
|
+
var position = $(this).position();
|
|
1081
|
+
var buttonHeight = $(this).height();
|
|
1082
|
+
var buttonWidth = $(this).width();
|
|
1083
|
+
var tooltipY = position.top - buttonHeight - 15;
|
|
1084
|
+
var centerTooltip = true;
|
|
1085
|
+
if ($(this).closest('div').hasClass('able-right-controls')) {
|
|
1086
|
+
// this control is on the right side
|
|
1087
|
+
if ($(this).closest('div').find('button:last').get(0) == $(this).get(0)) {
|
|
1088
|
+
// this is the last control on the right
|
|
1089
|
+
// position tooltip using the "right" property
|
|
1090
|
+
centerTooltip = false;
|
|
1091
|
+
var tooltipX = 0;
|
|
1092
|
+
var tooltipStyle = {
|
|
1093
|
+
left: '',
|
|
1094
|
+
right: tooltipX + 'px',
|
|
1095
|
+
top: tooltipY + 'px'
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
// this control is on the left side
|
|
1101
|
+
if ($(this).is(':first-child')) {
|
|
1102
|
+
// this is the first control on the left
|
|
1103
|
+
centerTooltip = false;
|
|
1104
|
+
var tooltipX = position.left;
|
|
1105
|
+
var tooltipStyle = {
|
|
1106
|
+
left: tooltipX + 'px',
|
|
1107
|
+
right: '',
|
|
1108
|
+
top: tooltipY + 'px'
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (centerTooltip) {
|
|
1113
|
+
// populate tooltip, then calculate its width before showing it
|
|
1114
|
+
var tooltipWidth = $('#' + tooltipId).text(label).width();
|
|
1115
|
+
// center the tooltip horizontally over the button
|
|
1116
|
+
var tooltipX = position.left - tooltipWidth/2;
|
|
1117
|
+
var tooltipStyle = {
|
|
1118
|
+
left: tooltipX + 'px',
|
|
1119
|
+
right: '',
|
|
1120
|
+
top: tooltipY + 'px'
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
var tooltip = $('#' + tooltipId).text(label).css(tooltipStyle);
|
|
1124
|
+
thisObj.showTooltip(tooltip);
|
|
1125
|
+
$(this).on('mouseleave blur',function() {
|
|
1126
|
+
$('#' + tooltipId).text('').hide();
|
|
1127
|
+
})
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
if (control === 'captions') {
|
|
1131
|
+
if (!this.prefCaptions || this.prefCaptions !== 1) {
|
|
1132
|
+
// captions are available, but user has them turned off
|
|
1133
|
+
if (this.captions.length > 1) {
|
|
1134
|
+
captionLabel = this.tt.captions;
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
captionLabel = this.tt.showCaptions;
|
|
1138
|
+
}
|
|
1139
|
+
newButton.addClass('buttonOff').attr('title',captionLabel);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
else if (control === 'descriptions') {
|
|
1143
|
+
if (!this.prefDesc || this.prefDesc !== 1) {
|
|
1144
|
+
// user prefer non-audio described version
|
|
1145
|
+
// Therefore, load media without description
|
|
1146
|
+
// Description can be toggled on later with this button
|
|
1147
|
+
newButton.addClass('buttonOff').attr('title',this.tt.turnOnDescriptions);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
$controllerSpan.append(newButton);
|
|
1152
|
+
|
|
1153
|
+
// create variables of buttons that are referenced throughout the AblePlayer object
|
|
1154
|
+
if (control === 'play') {
|
|
1155
|
+
this.$playpauseButton = newButton;
|
|
1156
|
+
}
|
|
1157
|
+
else if (control === 'captions') {
|
|
1158
|
+
this.$ccButton = newButton;
|
|
1159
|
+
}
|
|
1160
|
+
else if (control === 'sign') {
|
|
1161
|
+
this.$signButton = newButton;
|
|
1162
|
+
// gray out sign button if sign language window is not active
|
|
1163
|
+
if (!(this.$signWindow.is(':visible'))) {
|
|
1164
|
+
this.$signButton.addClass('buttonOff');
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
else if (control === 'descriptions') {
|
|
1168
|
+
this.$descButton = newButton;
|
|
1169
|
+
// button will be enabled or disabled in description.js > initDescription()
|
|
1170
|
+
}
|
|
1171
|
+
else if (control === 'mute') {
|
|
1172
|
+
this.$muteButton = newButton;
|
|
1173
|
+
}
|
|
1174
|
+
else if (control === 'transcript') {
|
|
1175
|
+
this.$transcriptButton = newButton;
|
|
1176
|
+
// gray out transcript button if transcript is not active
|
|
1177
|
+
if (!(this.$transcriptDiv.is(':visible'))) {
|
|
1178
|
+
this.$transcriptButton.addClass('buttonOff').attr('title',this.tt.showTranscript);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
else if (control === 'fullscreen') {
|
|
1182
|
+
this.$fullscreenButton = newButton;
|
|
1183
|
+
}
|
|
1184
|
+
else if (control === 'chapters') {
|
|
1185
|
+
this.$chaptersButton = newButton;
|
|
1186
|
+
}
|
|
1187
|
+
else if (control === 'preferences') {
|
|
1188
|
+
this.$prefsButton = newButton;
|
|
1189
|
+
}
|
|
1190
|
+
else if (control === 'volume') {
|
|
1191
|
+
this.$volumeButton = newButton;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
if (control === 'volume') {
|
|
1195
|
+
// in addition to the volume button, add a hidden slider
|
|
1196
|
+
this.addVolumeSlider($controllerSpan);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if ((i % 2) == 1) {
|
|
1200
|
+
this.$controllerDiv.append('<div style="clear:both;"></div>');
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (this.mediaType === 'video') {
|
|
1205
|
+
|
|
1206
|
+
if (typeof this.$captionsDiv !== 'undefined') {
|
|
1207
|
+
// stylize captions based on user prefs
|
|
1208
|
+
this.stylizeCaptions(this.$captionsDiv);
|
|
1209
|
+
}
|
|
1210
|
+
if (typeof this.$descDiv !== 'undefined') {
|
|
1211
|
+
// stylize descriptions based on user's caption prefs
|
|
1212
|
+
this.stylizeCaptions(this.$descDiv);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// combine left and right controls arrays for future reference
|
|
1217
|
+
this.controls = [];
|
|
1218
|
+
for (var sec in controlLayout) if (controlLayout.hasOwnProperty(sec)) {
|
|
1219
|
+
this.controls = this.controls.concat(controlLayout[sec]);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Update state-based display of controls.
|
|
1223
|
+
this.refreshControls();
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
AblePlayer.prototype.useSvg = function () {
|
|
1227
|
+
|
|
1228
|
+
// Modified from IcoMoon.io svgxuse
|
|
1229
|
+
// @copyright Copyright (c) 2016 IcoMoon.io
|
|
1230
|
+
// @license Licensed under MIT license
|
|
1231
|
+
// See https://github.com/Keyamoon/svgxuse
|
|
1232
|
+
// @version 1.1.16
|
|
1233
|
+
|
|
1234
|
+
var cache = Object.create(null); // holds xhr objects to prevent multiple requests
|
|
1235
|
+
var checkUseElems,
|
|
1236
|
+
tid; // timeout id
|
|
1237
|
+
var debouncedCheck = function () {
|
|
1238
|
+
clearTimeout(tid);
|
|
1239
|
+
tid = setTimeout(checkUseElems, 100);
|
|
1240
|
+
};
|
|
1241
|
+
var unobserveChanges = function () {
|
|
1242
|
+
return;
|
|
1243
|
+
};
|
|
1244
|
+
var observeChanges = function () {
|
|
1245
|
+
var observer;
|
|
1246
|
+
window.addEventListener('resize', debouncedCheck, false);
|
|
1247
|
+
window.addEventListener('orientationchange', debouncedCheck, false);
|
|
1248
|
+
if (window.MutationObserver) {
|
|
1249
|
+
observer = new MutationObserver(debouncedCheck);
|
|
1250
|
+
observer.observe(document.documentElement, {
|
|
1251
|
+
childList: true,
|
|
1252
|
+
subtree: true,
|
|
1253
|
+
attributes: true
|
|
1254
|
+
});
|
|
1255
|
+
unobserveChanges = function () {
|
|
1256
|
+
try {
|
|
1257
|
+
observer.disconnect();
|
|
1258
|
+
window.removeEventListener('resize', debouncedCheck, false);
|
|
1259
|
+
window.removeEventListener('orientationchange', debouncedCheck, false);
|
|
1260
|
+
} catch (ignore) {}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
else {
|
|
1264
|
+
document.documentElement.addEventListener('DOMSubtreeModified', debouncedCheck, false);
|
|
1265
|
+
unobserveChanges = function () {
|
|
1266
|
+
document.documentElement.removeEventListener('DOMSubtreeModified', debouncedCheck, false);
|
|
1267
|
+
window.removeEventListener('resize', debouncedCheck, false);
|
|
1268
|
+
window.removeEventListener('orientationchange', debouncedCheck, false);
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
var xlinkNS = 'http://www.w3.org/1999/xlink';
|
|
1273
|
+
checkUseElems = function () {
|
|
1274
|
+
var base,
|
|
1275
|
+
bcr,
|
|
1276
|
+
fallback = '', // optional fallback URL in case no base path to SVG file was given and no symbol definition was found.
|
|
1277
|
+
hash,
|
|
1278
|
+
i,
|
|
1279
|
+
Request,
|
|
1280
|
+
inProgressCount = 0,
|
|
1281
|
+
isHidden,
|
|
1282
|
+
url,
|
|
1283
|
+
uses,
|
|
1284
|
+
xhr;
|
|
1285
|
+
if (window.XMLHttpRequest) {
|
|
1286
|
+
Request = new XMLHttpRequest();
|
|
1287
|
+
if (Request.withCredentials !== undefined) {
|
|
1288
|
+
Request = XMLHttpRequest;
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
Request = XDomainRequest || undefined;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (Request === undefined) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
function observeIfDone() {
|
|
1298
|
+
// If done with making changes, start watching for chagnes in DOM again
|
|
1299
|
+
inProgressCount -= 1;
|
|
1300
|
+
if (inProgressCount === 0) { // if all xhrs were resolved
|
|
1301
|
+
observeChanges(); // watch for changes to DOM
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function attrUpdateFunc(spec) {
|
|
1305
|
+
return function () {
|
|
1306
|
+
if (cache[spec.base] !== true) {
|
|
1307
|
+
spec.useEl.setAttributeNS(xlinkNS, 'xlink:href', '#' + spec.hash);
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function onloadFunc(xhr) {
|
|
1312
|
+
return function () {
|
|
1313
|
+
var body = document.body;
|
|
1314
|
+
var x = document.createElement('x');
|
|
1315
|
+
var svg;
|
|
1316
|
+
xhr.onload = null;
|
|
1317
|
+
x.innerHTML = xhr.responseText;
|
|
1318
|
+
svg = x.getElementsByTagName('svg')[0];
|
|
1319
|
+
if (svg) {
|
|
1320
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
1321
|
+
svg.style.position = 'absolute';
|
|
1322
|
+
svg.style.width = 0;
|
|
1323
|
+
svg.style.height = 0;
|
|
1324
|
+
svg.style.overflow = 'hidden';
|
|
1325
|
+
body.insertBefore(svg, body.firstChild);
|
|
1326
|
+
}
|
|
1327
|
+
observeIfDone();
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function onErrorTimeout(xhr) {
|
|
1331
|
+
return function () {
|
|
1332
|
+
xhr.onerror = null;
|
|
1333
|
+
xhr.ontimeout = null;
|
|
1334
|
+
observeIfDone();
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
unobserveChanges(); // stop watching for changes to DOM
|
|
1338
|
+
// find all use elements
|
|
1339
|
+
uses = document.getElementsByTagName('use');
|
|
1340
|
+
for (i = 0; i < uses.length; i += 1) {
|
|
1341
|
+
try {
|
|
1342
|
+
bcr = uses[i].getBoundingClientRect();
|
|
1343
|
+
} catch (ignore) {
|
|
1344
|
+
// failed to get bounding rectangle of the use element
|
|
1345
|
+
bcr = false;
|
|
1346
|
+
}
|
|
1347
|
+
url = uses[i].getAttributeNS(xlinkNS, 'href').split('#');
|
|
1348
|
+
base = url[0];
|
|
1349
|
+
hash = url[1];
|
|
1350
|
+
isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0;
|
|
1351
|
+
if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) {
|
|
1352
|
+
// the use element is empty
|
|
1353
|
+
// if there is a reference to an external SVG, try to fetch it
|
|
1354
|
+
// use the optional fallback URL if there is no reference to an external SVG
|
|
1355
|
+
if (fallback && !base.length && hash && !document.getElementById(hash)) {
|
|
1356
|
+
base = fallback;
|
|
1357
|
+
}
|
|
1358
|
+
if (base.length) {
|
|
1359
|
+
// schedule updating xlink:href
|
|
1360
|
+
xhr = cache[base];
|
|
1361
|
+
if (xhr !== true) {
|
|
1362
|
+
// true signifies that prepending the SVG was not required
|
|
1363
|
+
setTimeout(attrUpdateFunc({
|
|
1364
|
+
useEl: uses[i],
|
|
1365
|
+
base: base,
|
|
1366
|
+
hash: hash
|
|
1367
|
+
}), 0);
|
|
1368
|
+
}
|
|
1369
|
+
if (xhr === undefined) {
|
|
1370
|
+
xhr = new Request();
|
|
1371
|
+
cache[base] = xhr;
|
|
1372
|
+
xhr.onload = onloadFunc(xhr);
|
|
1373
|
+
xhr.onerror = onErrorTimeout(xhr);
|
|
1374
|
+
xhr.ontimeout = onErrorTimeout(xhr);
|
|
1375
|
+
xhr.open('GET', base);
|
|
1376
|
+
xhr.send();
|
|
1377
|
+
inProgressCount += 1;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
if (!isHidden) {
|
|
1383
|
+
if (cache[base] === undefined) {
|
|
1384
|
+
// remember this URL if the use element was not empty and no request was sent
|
|
1385
|
+
cache[base] = true;
|
|
1386
|
+
}
|
|
1387
|
+
else if (cache[base].onload) {
|
|
1388
|
+
// if it turns out that prepending the SVG is not necessary,
|
|
1389
|
+
// abort the in-progress xhr.
|
|
1390
|
+
cache[base].abort();
|
|
1391
|
+
cache[base].onload = undefined;
|
|
1392
|
+
cache[base] = true;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
uses = '';
|
|
1398
|
+
inProgressCount += 1;
|
|
1399
|
+
observeIfDone();
|
|
1400
|
+
};
|
|
1401
|
+
/*
|
|
1402
|
+
// The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty.
|
|
1403
|
+
window.addEventListener('load', function winLoad() {
|
|
1404
|
+
window.removeEventListener('load', winLoad, false); // to prevent memory leaks
|
|
1405
|
+
tid = setTimeout(checkUseElems, 0);
|
|
1406
|
+
}, false);
|
|
1407
|
+
*/
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
AblePlayer.prototype.swapSource = function(sourceIndex) {
|
|
1411
|
+
|
|
1412
|
+
// Change media player source file, for instance when moving to the next element in a playlist.
|
|
1413
|
+
// NOTE: Swapping source for audio description is handled elsewhere;
|
|
1414
|
+
// see description.js > swapDescription()
|
|
1415
|
+
|
|
1416
|
+
var $newItem, itemTitle, itemLang, sources, s, jwSource, i, $newSource, nowPlayingSpan;
|
|
1417
|
+
|
|
1418
|
+
this.$media.find('source').remove();
|
|
1419
|
+
$newItem = this.$playlist.eq(sourceIndex);
|
|
1420
|
+
itemTitle = $newItem.html();
|
|
1421
|
+
if ($newItem.attr('lang')) {
|
|
1422
|
+
itemLang = $newItem.attr('lang');
|
|
1423
|
+
}
|
|
1424
|
+
sources = [];
|
|
1425
|
+
s = 0; // index
|
|
1426
|
+
if (this.mediaType === 'audio') {
|
|
1427
|
+
if ($newItem.attr('data-mp3')) {
|
|
1428
|
+
jwSource = $newItem.attr('data-mp3'); // JW Player can play this
|
|
1429
|
+
sources[s] = new Array('audio/mpeg',jwSource);
|
|
1430
|
+
s++;
|
|
1431
|
+
}
|
|
1432
|
+
if ($newItem.attr('data-webm')) {
|
|
1433
|
+
sources[s] = new Array('audio/webm',$newItem.attr('data-webm'));
|
|
1434
|
+
s++;
|
|
1435
|
+
}
|
|
1436
|
+
if ($newItem.attr('data-webma')) {
|
|
1437
|
+
sources[s] = new Array('audio/webm',$newItem.attr('data-webma'));
|
|
1438
|
+
s++;
|
|
1439
|
+
}
|
|
1440
|
+
if ($newItem.attr('data-ogg')) {
|
|
1441
|
+
sources[s] = new Array('audio/ogg',$newItem.attr('data-ogg'));
|
|
1442
|
+
s++;
|
|
1443
|
+
}
|
|
1444
|
+
if ($newItem.attr('data-oga')) {
|
|
1445
|
+
sources[s] = new Array('audio/ogg',$newItem.attr('data-oga'));
|
|
1446
|
+
s++;
|
|
1447
|
+
}
|
|
1448
|
+
if ($newItem.attr('data-wav')) {
|
|
1449
|
+
sources[s] = new Array('audio/wav',$newItem.attr('data-wav'));
|
|
1450
|
+
s++;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
else if (this.mediaType === 'video') {
|
|
1454
|
+
if ($newItem.attr('data-mp4')) {
|
|
1455
|
+
jwSource = $newItem.attr('data-mp4'); // JW Player can play this
|
|
1456
|
+
sources[s] = new Array('video/mp4',jwSource);
|
|
1457
|
+
s++;
|
|
1458
|
+
}
|
|
1459
|
+
if ($newItem.attr('data-webm')) {
|
|
1460
|
+
sources[s] = new Array('video/webm',$newItem.attr('data-webm'));
|
|
1461
|
+
s++;
|
|
1462
|
+
}
|
|
1463
|
+
if ($newItem.attr('data-webmv')) {
|
|
1464
|
+
sources[s] = new Array('video/webm',$newItem.attr('data-webmv'));
|
|
1465
|
+
s++;
|
|
1466
|
+
}
|
|
1467
|
+
if ($newItem.attr('data-ogg')) {
|
|
1468
|
+
sources[s] = new Array('video/ogg',$newItem.attr('data-ogg'));
|
|
1469
|
+
s++;
|
|
1470
|
+
}
|
|
1471
|
+
if ($newItem.attr('data-ogv')) {
|
|
1472
|
+
sources[s] = new Array('video/ogg',$newItem.attr('data-ogv'));
|
|
1473
|
+
s++;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
for (i=0; i<sources.length; i++) {
|
|
1477
|
+
$newSource = $('<source>',{
|
|
1478
|
+
type: sources[i][0],
|
|
1479
|
+
src: sources[i][1]
|
|
1480
|
+
});
|
|
1481
|
+
this.$media.append($newSource);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// update playlist to indicate which item is playing
|
|
1485
|
+
//$('.able-playlist li').removeClass('able-current');
|
|
1486
|
+
this.$playlist.removeClass('able-current');
|
|
1487
|
+
$newItem.addClass('able-current');
|
|
1488
|
+
|
|
1489
|
+
// update Now Playing div
|
|
1490
|
+
if (this.showNowPlaying === true) {
|
|
1491
|
+
nowPlayingSpan = $('<span>');
|
|
1492
|
+
if (typeof itemLang !== 'undefined') {
|
|
1493
|
+
nowPlayingSpan.attr('lang',itemLang);
|
|
1494
|
+
}
|
|
1495
|
+
nowPlayingSpan.html('<span>Selected track:</span>' + itemTitle);
|
|
1496
|
+
this.$nowPlayingDiv.html(nowPlayingSpan);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// reload audio after sources have been updated
|
|
1500
|
+
// if this.swappingSrc is true, media will autoplay when ready
|
|
1501
|
+
if (this.initializing) { // this is the first track - user hasn't pressed play yet
|
|
1502
|
+
this.swappingSrc = false;
|
|
1503
|
+
}
|
|
1504
|
+
else {
|
|
1505
|
+
this.swappingSrc = true;
|
|
1506
|
+
if (this.player === 'html5') {
|
|
1507
|
+
this.media.load();
|
|
1508
|
+
}
|
|
1509
|
+
else if (this.player === 'jw') {
|
|
1510
|
+
this.jwPlayer.load({file: jwSource});
|
|
1511
|
+
}
|
|
1512
|
+
else if (this.player === 'youtube') {
|
|
1513
|
+
// Does nothing, can't swap source with youtube.
|
|
1514
|
+
// TODO: Anything we need to do to prevent this happening?
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
|
|
1519
|
+
AblePlayer.prototype.getButtonTitle = function(control) {
|
|
1520
|
+
|
|
1521
|
+
var captionsCount;
|
|
1522
|
+
|
|
1523
|
+
if (control === 'playpause') {
|
|
1524
|
+
return this.tt.play;
|
|
1525
|
+
}
|
|
1526
|
+
else if (control === 'play') {
|
|
1527
|
+
return this.tt.play;
|
|
1528
|
+
}
|
|
1529
|
+
else if (control === 'pause') {
|
|
1530
|
+
return this.tt.pause;
|
|
1531
|
+
}
|
|
1532
|
+
else if (control === 'restart') {
|
|
1533
|
+
return this.tt.restart;
|
|
1534
|
+
}
|
|
1535
|
+
else if (control === 'rewind') {
|
|
1536
|
+
return this.tt.rewind;
|
|
1537
|
+
}
|
|
1538
|
+
else if (control === 'forward') {
|
|
1539
|
+
return this.tt.forward;
|
|
1540
|
+
}
|
|
1541
|
+
else if (control === 'captions') {
|
|
1542
|
+
if (this.usingYouTubeCaptions) {
|
|
1543
|
+
captionsCount = this.ytCaptions.length;
|
|
1544
|
+
}
|
|
1545
|
+
else {
|
|
1546
|
+
captionsCount = this.captions.length;
|
|
1547
|
+
}
|
|
1548
|
+
if (captionsCount > 1) {
|
|
1549
|
+
return this.tt.captions;
|
|
1550
|
+
}
|
|
1551
|
+
else {
|
|
1552
|
+
if (this.captionsOn) {
|
|
1553
|
+
return this.tt.hideCaptions;
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
return this.tt.showCaptions;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
else if (control === 'descriptions') {
|
|
1561
|
+
if (this.descOn) {
|
|
1562
|
+
return this.tt.turnOffDescriptions;
|
|
1563
|
+
}
|
|
1564
|
+
else {
|
|
1565
|
+
return this.tt.turnOnDescriptions;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
else if (control === 'transcript') {
|
|
1569
|
+
if (this.$transcriptDiv.is(':visible')) {
|
|
1570
|
+
return this.tt.hideTranscript;
|
|
1571
|
+
}
|
|
1572
|
+
else {
|
|
1573
|
+
return this.tt.showTranscript;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
else if (control === 'chapters') {
|
|
1577
|
+
return this.tt.chapters;
|
|
1578
|
+
}
|
|
1579
|
+
else if (control === 'sign') {
|
|
1580
|
+
return this.tt.sign;
|
|
1581
|
+
}
|
|
1582
|
+
else if (control === 'volume') {
|
|
1583
|
+
return this.tt.volume;
|
|
1584
|
+
}
|
|
1585
|
+
else if (control === 'faster') {
|
|
1586
|
+
return this.tt.faster;
|
|
1587
|
+
}
|
|
1588
|
+
else if (control === 'slower') {
|
|
1589
|
+
return this.tt.slower;
|
|
1590
|
+
}
|
|
1591
|
+
else if (control === 'preferences') {
|
|
1592
|
+
return this.tt.preferences;
|
|
1593
|
+
}
|
|
1594
|
+
else if (control === 'help') {
|
|
1595
|
+
// return this.tt.help;
|
|
1596
|
+
}
|
|
1597
|
+
else {
|
|
1598
|
+
// there should be no other controls, but just in case:
|
|
1599
|
+
// return the name of the control with first letter in upper case
|
|
1600
|
+
// ultimately will need to get a translated label from this.tt
|
|
1601
|
+
if (this.debug) {
|
|
1602
|
+
console.log('Found an untranslated label: ' + control);
|
|
1603
|
+
}
|
|
1604
|
+
return control.charAt(0).toUpperCase() + control.slice(1);
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
})(jQuery);
|