Skip to content

Commit a93a2ac

Browse files
committed
Add sign language support
1 parent cd02fc5 commit a93a2ac

21 files changed

+570
-52
lines changed

Gruntfile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ module.exports = function(grunt) {
3333
'scripts/translation.js',
3434
'scripts/transcript.js',
3535
'scripts/search.js',
36-
'scripts/event.js'
36+
'scripts/event.js',
37+
'scripts/sign.js'
3738
],
3839
dest: 'build/<%= pkg.name %>.js'
3940
},

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,29 @@ alternate video with description mixed in, that’s what they’ll get. See
257257
the section below on *User Preferences* for additional information about
258258
preferences.
259259

260+
#### Sign language
261+
262+
Sign language translation is supported in a separate video player,
263+
synchronized with the main player. Tips for filming a sign language
264+
interpreter are available from [Signing Books for the Deaf][]:
265+
266+
* [Filming the Signer][]
267+
* [Editing the Signer][]
268+
269+
If multiple video sources are already provided (e.g., an MP4 and
270+
WebM file), then the sign language video must be available in both of
271+
these formats. For each video source that has a sign language version
272+
available, add a **data-sign-src** attribute to the \<source\> element for
273+
that video. The value of this attribute is a path pointing to the
274+
sign language version of the video. If a sign language version is available,
275+
a sign language button will be added to the media controller.
276+
This button will toggle the display of a secondary window in which
277+
the sign language video will appear.
278+
279+
This is an experimental feature and a work in progress. Ultimately
280+
the intent is for the user to have full control of the size and position
281+
of the sign language video.
282+
260283
Setup Step 4: Review User-Defined Variables in *ableplayer.js*
261284
--------------------------------------------------------------
262285

@@ -478,6 +501,8 @@ Files created by the build process are put into the */build* directory:
478501
[Configuring MIME Types in IIS 7]: http://technet.microsoft.com/en-us/library/17bda1f4-8a0d-440f-986a-5aaa9d40b74c.aspx
479502
[How to add MIME Types with IIS7 Web.config]: http://blogs.iis.net/bills/archive/2008/03/25/how-to-add-mime-types-with-iis7-web-config.aspx
480503
[npm]: https://www.npmjs.com/
481-
[grunt]: http://gruntjs.com/
482-
504+
[Grunt]: http://gruntjs.com/
505+
[Signing Books for the Deaf]: http://www.sign-lang.uni-hamburg.de/signingbooks/
506+
[Filming the Signer]: http://www.sign-lang.uni-hamburg.de/signingbooks/sbrc/grid/d71/guide12.htm
507+
[Editing the Signer]: http://www.sign-lang.uni-hamburg.de/signingbooks/sbrc/grid/d71/guide13.htm
483508

build/ableplayer.dist.js

Lines changed: 158 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@
189189
this.searchDiv = $(media).data('search-div');
190190
}
191191
}
192-
192+
193193
this.ableIndex = AblePlayer.nextIndex;
194194
AblePlayer.nextIndex += 1;
195195

@@ -553,6 +553,7 @@
553553

554554
this.loadCurrentPreferences();
555555
this.injectPlayerCode();
556+
this.initSignLanguage();
556557
this.setupTracks().then(function () {
557558
thisObj.setupPopups();
558559
thisObj.initDescription();
@@ -946,6 +947,12 @@
946947
'label': this.tt.prefCaptions,
947948
'default': 1 // on because many users can benefit
948949
});
950+
951+
prefs.push({
952+
'name': 'prefSignLanguage', // use sign language if available
953+
'label': this.tt.prefSignLanguage,
954+
'default': 1 // on because in rare cases that it's actually available, users should be exposed to it
955+
});
949956

950957
prefs.push({
951958
'name': 'prefDesc', // audio description default state
@@ -2145,21 +2152,35 @@
21452152
if (this.transcriptDivLocation) {
21462153
$('#' + this.transcriptDivLocation).append(this.$transcriptArea);
21472154
}
2155+
else if (this.$ableColumnRight) {
2156+
this.$ableColumnRight.prepend(this.$transcriptArea);
2157+
}
21482158
else {
2149-
// Place adjacent to player with reactive flow.
2150-
this.$ableColumnLeft = this.$ableDiv.wrap('<div class="able-column-left">').parent();
2151-
this.$ableColumnLeft.width(this.playerWidth);
2152-
this.$transcriptArea.insertAfter(this.$ableColumnLeft);
2153-
this.$ableColumnRight = this.$transcriptArea.wrap('<div class="able-column-right">').parent();
2154-
this.$ableColumnRight.width(this.playerWidth);
2159+
this.splitPlayerIntoColumns('transcript');
21552160
}
2156-
2161+
21572162
// If client has provided separate transcript location, override user's preference for hiding transcript
21582163
if (!this.prefTranscript && !this.transcriptDivLocation) {
21592164
this.$transcriptArea.hide();
21602165
}
21612166
};
21622167

2168+
AblePlayer.prototype.splitPlayerIntoColumns = function (feature) {
2169+
// feature is either 'transcript' or 'sign'
2170+
// if present, player is split into two column, with this feature in the right column
2171+
this.$ableColumnLeft = this.$ableDiv.wrap('<div class="able-column-left">').parent();
2172+
this.$ableColumnLeft.width(this.playerWidth);
2173+
if (feature === 'transcript') {
2174+
this.$transcriptArea.insertAfter(this.$ableColumnLeft);
2175+
this.$ableColumnRight = this.$transcriptArea.wrap('<div class="able-column-right">').parent();
2176+
}
2177+
else if (feature == 'sign') {
2178+
this.$signArea.insertAfter(this.$ableColumnLeft);
2179+
this.$ableColumnRight = this.$signArea.wrap('<div class="able-column-right">').parent();
2180+
}
2181+
this.$ableColumnRight.width(this.playerWidth);
2182+
};
2183+
21632184
AblePlayer.prototype.injectAlert = function () {
21642185
this.alertBox = $('<div role="alert"></div>');
21652186
this.alertBox.addClass('able-tooltip');
@@ -2652,12 +2673,12 @@
26522673
if (this.hasCaptions) {
26532674
blr.push('captions'); //closed captions
26542675
}
2655-
if (this.hasOpenDesc || this.hasClosedDesc) {
2656-
blr.push('descriptions'); //audio description
2657-
}
26582676
if (this.hasSignLanguage) {
26592677
blr.push('sign'); // sign language
26602678
}
2679+
if (this.hasOpenDesc || this.hasClosedDesc) {
2680+
blr.push('descriptions'); //audio description
2681+
}
26612682
}
26622683

26632684
if (this.includeTranscript && this.useTranscriptButton) {
@@ -2902,6 +2923,9 @@
29022923
else if (control === 'captions') {
29032924
this.$ccButton = newButton;
29042925
}
2926+
else if (control === 'sign') {
2927+
this.$signButton = newButton;
2928+
}
29052929
else if (control === 'descriptions') {
29062930
this.$descButton = newButton;
29072931
// gray out description button if description is not active
@@ -3113,7 +3137,7 @@
31133137
else if (control === 'chapters') {
31143138
return this.tt.chapters;
31153139
}
3116-
else if (control === 'sign') { // not yet supported
3140+
else if (control === 'sign') {
31173141
return this.tt.sign;
31183142
}
31193143
else if (control === 'mute') {
@@ -4271,6 +4295,12 @@
42714295

42724296
if (seekable.length > 0 && this.startTime >= seekable.start(0) && this.startTime <= seekable.end(0)) {
42734297
this.media.currentTime = this.startTime;
4298+
4299+
if (this.hasSignLanguage && this.signVideo) {
4300+
// keep sign languge video in sync
4301+
this.signVideo.currentTime = this.startTime;
4302+
}
4303+
42744304
}
42754305
}
42764306
else if (this.player === 'jw') {
@@ -4453,6 +4483,9 @@
44534483

44544484
if (this.player === 'html5') {
44554485
this.media.volume = volume;
4486+
if (this.hasSignLanguage && this.signVideo) {
4487+
this.signVideo.volume = 0; // always mute
4488+
}
44564489
}
44574490
else if (this.player === 'jw') {
44584491
this.jwPlayer.setVolume(volume * 100);
@@ -4529,6 +4562,9 @@
45294562
AblePlayer.prototype.pauseMedia = function () {
45304563
if (this.player === 'html5') {
45314564
this.media.pause(true);
4565+
if (this.hasSignLanguage && this.signVideo) {
4566+
this.signVideo.pause(true);
4567+
}
45324568
}
45334569
else if (this.player === 'jw') {
45344570
this.jwPlayer.pause(true);
@@ -4541,6 +4577,9 @@
45414577
AblePlayer.prototype.playMedia = function () {
45424578
if (this.player === 'html5') {
45434579
this.media.play(true);
4580+
if (this.hasSignLanguage && this.signVideo) {
4581+
this.signVideo.play(true);
4582+
}
45444583
}
45454584
else if (this.player === 'jw') {
45464585
this.jwPlayer.play(true);
@@ -5055,6 +5094,19 @@
50555094
}
50565095
};
50575096

5097+
AblePlayer.prototype.handleSignToggle = function () {
5098+
if (this.$signWindow.is(':visible')) {
5099+
this.$signWindow.hide();
5100+
this.$signButton.addClass('buttonOff').attr('aria-label',this.tt.showSign);
5101+
this.$signButton.find('span.able-clipped').text(this.tt.showSign);
5102+
}
5103+
else {
5104+
this.$signWindow.show();
5105+
this.$signButton.removeClass('buttonOff').attr('aria-label',this.tt.hideSign);
5106+
this.$signButton.find('span.able-clipped').text(this.tt.hideSign);
5107+
}
5108+
};
5109+
50585110
AblePlayer.prototype.isFullscreen = function () {
50595111
if (this.nativeFullscreenSupported()) {
50605112
return (document.fullscreenElement ||
@@ -6062,8 +6114,8 @@
60626114
else if (whichButton === 'descriptions') {
60636115
this.handleDescriptionToggle();
60646116
}
6065-
else if (whichButton.substr(0,4) === 'sign') {
6066-
// not yet supported
6117+
else if (whichButton === 'sign') {
6118+
this.handleSignToggle();
60676119
}
60686120
else if (whichButton === 'preferences') {
60696121
this.handlePrefsClick();
@@ -6423,3 +6475,95 @@
64236475
}
64246476
};
64256477
})(jQuery);
6478+
6479+
(function ($) {
6480+
AblePlayer.prototype.initSignLanguage = function() {
6481+
6482+
// only initialize sign language if user wants it
6483+
// since it requires downloading a second video & consumes bandwidth
6484+
if (this.prefSignLanguage) {
6485+
// check to see if there's a sign language video accompanying this video
6486+
// check only the first source
6487+
// If sign language is provided, it must be provided for all sources
6488+
this.signFile = this.$sources.first().attr('data-sign-src');
6489+
if (this.signFile) {
6490+
if (this.debug) {
6491+
6492+
}
6493+
this.hasSignLanguage = true;
6494+
this.injectSignPlayerCode();
6495+
}
6496+
else {
6497+
this.hasSignLanguage = false;
6498+
}
6499+
}
6500+
};
6501+
6502+
AblePlayer.prototype.injectSignPlayerCode = function() {
6503+
6504+
// create and inject surrounding HTML structure
6505+
// If IOS:
6506+
// If video:
6507+
// IOS does not support any of the player's functionality
6508+
// - everything plays in its own player
6509+
// Therefore, AblePlayer is not loaded & all functionality is disabled
6510+
// (this all determined. If this is IOS && video, this function is never called)
6511+
// If audio:
6512+
// HTML cannot be injected as a *parent* of the <audio> element
6513+
// It is therefore injected *after* the <audio> element
6514+
// This is only a problem in IOS 6 and earlier,
6515+
// & is a known bug, fixed in IOS 7
6516+
6517+
var thisObj, signVideoId, i, signSrc, srcType, $signSource;
6518+
6519+
thisObj = this;
6520+
6521+
signVideoId = this.mediaId + '-sign';
6522+
this.$signVideo = $('<video>',{
6523+
'id' : signVideoId,
6524+
'width' : this.playerWidth
6525+
});
6526+
this.signVideo = this.$signVideo[0];
6527+
// for each original <source>, add a <source> to the sign <video>
6528+
for (i=0; i < this.$sources.length; i++) {
6529+
signSrc = this.$sources[i].getAttribute('data-sign-src');
6530+
srcType = this.$sources[i].getAttribute('type');
6531+
if (signSrc) {
6532+
$signSource = $('<source>',{
6533+
'src' : signSrc,
6534+
'type' : srcType
6535+
});
6536+
this.$signVideo.append($signSource);
6537+
}
6538+
else {
6539+
// source is missing a sign language version
6540+
// can't include sign language
6541+
this.hasSignLanguage = false;
6542+
break;
6543+
}
6544+
}
6545+
6546+
// TODO: Consider whether width x height should be added to the sign window, or the video element
6547+
this.$signWindow = $('<div>',{
6548+
'class' : 'able-sign-window',
6549+
});
6550+
this.$signWindow.append(this.$signVideo).hide();
6551+
6552+
// Place sign window in div.able-column-right
6553+
// If div doesn't exist yet, create it
6554+
if (this.$ableColumnRight) {
6555+
this.$ableColumnRight.append(this.$signWindow);
6556+
}
6557+
else {
6558+
this.splitPlayerIntoColumns('sign');
6559+
}
6560+
6561+
this.addSignEvents();
6562+
};
6563+
6564+
AblePlayer.prototype.addSignEvents = function() {
6565+
6566+
// populate with functions to handle click and drag on sign window
6567+
};
6568+
6569+
})(jQuery);

0 commit comments

Comments
 (0)