',{
- 'class': 'able-search-result-text'
- })
- itemText.html('...' + resultsArray[i]['caption'] + '...');
- resultsItem.append(itemStartSpan, itemText);
- resultsList.append(resultsItem);
- }
- $('#' + this.searchDiv).append(resultsSummary, resultsList);
- }
- else {
- var noResults = $('').text('No results found.');
- $('#' + this.searchDiv).append(noResults);
- }
- }
- }
- };
-
- AblePlayer.prototype.searchFor = function(searchString) {
-
- // return chronological array of caption cues that match searchTerms
- var captionLang, captions, results, caption, c, i, j;
- results = [];
- // split searchTerms into an array
- var searchTerms = searchString.split(' ');
- if (this.captions.length > 0) {
- // Get caption track that matches this.searchLang
- for (i=0; i < this.captions.length; i++) {
- if (this.captions[i].language === this.searchLang) {
- captionLang = this.searchLang;
- captions = this.captions[i].cues;
- }
- }
- if (captions.length > 0) {
- c = 0;
- for (i = 0; i < captions.length; i++) {
- if ($.inArray(captions[i].components.children[0]['type'], ['string','i','b','u','v','c']) !== -1) {
- caption = this.flattenCueForCaption(captions[i]);
- for (j = 0; j < searchTerms.length; j++) {
- if (caption.indexOf(searchTerms[j]) !== -1) {
- results[c] = [];
- results[c]['start'] = captions[i].start;
- results[c]['lang'] = captionLang;
- results[c]['caption'] = this.highlightSearchTerm(searchTerms,j,caption);
- c++;
- break;
- }
- }
- }
- }
- }
- }
- return results;
- };
-
- AblePlayer.prototype.highlightSearchTerm = function(searchTerms, index, resultString) {
-
- // highlight ALL found searchTerms in the current resultString
- // index is the first index in the searchTerm array where a match has already been found
- // Need to step through the remaining terms to see if they're present as well
-
- var i, searchTerm, termIndex, termLength, str1, str2, str3;
-
- for (i=index; i 0) {
- str1 = resultString.substring(0, termIndex);
- str2 = '' + searchTerm + ' ';
- str3 = resultString.substring(termIndex+termLength);
- resultString = str1 + str2 + str3;
- }
- else {
- str1 = '' + searchTerm + ' ';
- str2 = resultString.substring(termIndex+termLength);
- resultString = str1 + str2;
- }
- }
- }
- return resultString;
- };
-
- AblePlayer.prototype.secondsToTime = function(totalSeconds) {
-
- // return an array of totalSeconds converted into two formats
- // time['value'] = HH:MM:SS with hours dropped if there are none
- // time['title'] = a speakable rendering, so speech rec users can easily speak the link
-
- // first, round down to nearest second
- var totalSeconds = Math.floor(totalSeconds);
-
- var hours = parseInt( totalSeconds / 3600 , 10) % 24;
- var minutes = parseInt( totalSeconds / 60 , 10) % 60;
- var seconds = totalSeconds % 60;
- var value = '';
- var title = '';
- if (hours > 0) {
- value += hours + ':';
- title + hours + ' hours ';
- }
- if (minutes < 10) {
- value += '0' + minutes + ':';
- if (minutes > 0) {
- title += minutes + ' minutes ';
- }
- }
- else {
- value += minutes + ':';
- title += minutes + ' minutes ';
- }
- if (seconds < 10) {
- value += '0' + seconds;
- if (seconds > 0) {
- title += seconds + ' seconds ';
- }
- }
- else {
- value += seconds;
- title += seconds + ' seconds ';
- }
- var time = [];
- time['value'] = value;
- time['title'] = title;
- return time;
- };
-})(jQuery);
-
-(function ($) {
- // Media events
- AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) {
-
- // duration and elapsed are passed from callback functions of Vimeo API events
- // duration is expressed as sss.xxx
- // elapsed is expressed as sss.xxx
- var thisObj = this;
- this.getMediaTimes(duration,elapsed).then(function(mediaTimes) {
- thisObj.duration = mediaTimes['duration'];
- thisObj.elapsed = mediaTimes['elapsed'];
- if (thisObj.swappingSrc && (typeof thisObj.swapTime !== 'undefined')) {
- if (thisObj.swapTime === thisObj.elapsed) {
- // described version been swapped and media has scrubbed to time of previous version
- if (thisObj.playing) {
- // resume playback
- thisObj.playMedia();
- // reset vars
- thisObj.swappingSrc = false;
- thisObj.swapTime = null;
- }
- }
- }
- else if (thisObj.startedPlaying) {
- // do all the usual time-sync stuff during playback
- if (thisObj.prefHighlight === 1) {
- thisObj.highlightTranscript(thisObj.elapsed);
- }
- thisObj.updateCaption(thisObj.elapsed);
- thisObj.showDescription(thisObj.elapsed);
- thisObj.updateChapter(thisObj.elapsed);
- thisObj.updateMeta(thisObj.elapsed);
- thisObj.refreshControls('timeline', thisObj.duration, thisObj.elapsed);
- }
- });
- };
-
- AblePlayer.prototype.onMediaPause = function () {
-
- if (this.controlsHidden) {
- this.fadeControls('in');
- this.controlsHidden = false;
- }
- if (this.hideControlsTimeoutStatus === 'active') {
- window.clearTimeout(this.hideControlsTimeout);
- this.hideControlsTimeoutStatus = 'clear';
-
- }
- this.refreshControls('playpause');
- };
-
- AblePlayer.prototype.onMediaComplete = function () {
- // if there's a playlist, advance to next item and start playing
- if (this.hasPlaylist && !this.cueingPlaylistItem) {
- if (this.playlistIndex === (this.$playlist.length - 1)) {
- // this is the last track in the playlist
- if (this.loop) {
- this.playlistIndex = 0;
- this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
- this.cuePlaylistItem(0);
- }
- else {
- this.playing = false;
- this.paused = true;
- }
- }
- else {
- // this is not the last track. Play the next one.
- this.playlistIndex++;
- this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
- this.cuePlaylistItem(this.playlistIndex)
- }
- }
- this.refreshControls('init');
- };
-
- AblePlayer.prototype.onMediaNewSourceLoad = function () {
-
- if (this.cueingPlaylistItem) {
- // this variable was set in order to address bugs caused by multiple firings of media 'end' event
- // safe to reset now
- this.cueingPlaylistItem = false;
- }
- if (this.swappingSrc === true) {
- // new source file has just been loaded
- if (this.swapTime > 0) {
- // this.swappingSrc will be set to false after seek is complete
- // see onMediaUpdateTime()
- this.seekTo(this.swapTime);
- }
- else {
- if (this.playing) {
- // should be able to resume playback
- this.playMedia();
- }
- this.swappingSrc = false; // swapping is finished
- }
- }
- this.refreshControls('init');
- };
-
- // End Media events
-
- AblePlayer.prototype.onWindowResize = function () {
-
- if (this.fullscreen) { // replace isFullscreen() with a Boolean. see function for explanation
-
- var newWidth, newHeight;
-
- newWidth = $(window).width();
-
- // haven't isolated why, but some browsers return an innerHeight that's 20px too tall in fullscreen mode
- // Test results:
- // Browsers that require a 20px adjustment: Firefox, IE11 (Trident), Edge
- if (this.isUserAgent('Firefox') || this.isUserAgent('Trident') || this.isUserAgent('Edge')) {
- newHeight = window.innerHeight - this.$playerDiv.outerHeight() - 20;
- }
- else if (window.outerHeight >= window.innerHeight) {
- // Browsers that do NOT require adjustment: Chrome, Safari, Opera, MSIE 10
- newHeight = window.innerHeight - this.$playerDiv.outerHeight();
- }
- else {
- // Observed in Safari 9.0.1 on Mac OS X: outerHeight is actually less than innerHeight
- // Maybe a bug, or maybe window.outerHeight is already adjusted for controller height(?)
- // No longer observed in Safari 9.0.2
- newHeight = window.outerHeight;
- }
- if (!this.$descDiv.is(':hidden')) {
- newHeight -= this.$descDiv.height();
- }
- this.positionCaptions('overlay');
- }
- else { // not fullscreen
- if (this.restoringAfterFullScreen) {
- newWidth = this.preFullScreenWidth;
- newHeight = this.preFullScreenHeight;
- }
- else {
- // not restoring after full screen
- newWidth = this.$ableWrapper.width();
- if (typeof this.aspectRatio !== 'undefined') {
- newHeight = Math.round(newWidth / this.aspectRatio);
- }
- else {
- // not likely, since this.aspectRatio is defined during intialization
- // however, this is a fallback scenario just in case
- newHeight = this.$ableWrapper.height();
- }
- this.positionCaptions(); // reset with this.prefCaptionsPosition
- }
- }
- this.resizePlayer(newWidth, newHeight);
- };
-
- AblePlayer.prototype.addSeekbarListeners = function () {
-
- var thisObj = this;
-
- // Handle seek bar events.
- this.seekBar.bodyDiv.on('startTracking', function (e) {
- thisObj.pausedBeforeTracking = thisObj.paused;
- thisObj.pauseMedia();
- }).on('tracking', function (e, position) {
- // Scrub transcript, captions, and metadata.
- thisObj.highlightTranscript(position);
- thisObj.updateCaption(position);
- thisObj.showDescription(position);
- thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position));
- thisObj.updateMeta(position);
- thisObj.refreshControls('init');
- }).on('stopTracking', function (e, position) {
- if (thisObj.useChapterTimes) {
- thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position));
- }
- else {
- thisObj.seekTo(position);
- }
- if (!thisObj.pausedBeforeTracking) {
- setTimeout(function () {
- thisObj.playMedia();
- }, 200);
- }
- });
- };
-
- AblePlayer.prototype.onClickPlayerButton = function (el) {
-
- // TODO: This is super-fragile since we need to know the length of the class name to split off; update this to other way of dispatching?
-
- var whichButton, prefsPopup;
- whichButton = $(el).attr('class').split(' ')[0].substr(20);
- if (whichButton === 'play') {
- this.clickedPlay = true;
- this.handlePlay();
- }
- else if (whichButton === 'restart') {
- this.seekTrigger = 'restart';
- this.handleRestart();
- }
- else if (whichButton === 'previous') {
- this.seekTrigger = 'previous';
- this.buttonWithFocus = 'previous';
- this.handlePrevTrack();
- }
- else if (whichButton === 'next') {
- this.seekTrigger = 'next';
- this.buttonWithFocus = 'next';
- this.handleNextTrack();
- }
- else if (whichButton === 'rewind') {
- this.seekTrigger = 'rewind';
- this.handleRewind();
- }
- else if (whichButton === 'forward') {
- this.seekTrigger = 'forward';
- this.handleFastForward();
- }
- else if (whichButton === 'mute') {
- this.handleMute();
- }
- else if (whichButton === 'volume') {
- this.handleVolume();
- }
- else if (whichButton === 'faster') {
- this.handleRateIncrease();
- }
- else if (whichButton === 'slower') {
- this.handleRateDecrease();
- }
- else if (whichButton === 'captions') {
- this.handleCaptionToggle();
- }
- else if (whichButton === 'chapters') {
- this.handleChapters();
- }
- else if (whichButton === 'descriptions') {
- this.handleDescriptionToggle();
- }
- else if (whichButton === 'sign') {
- this.handleSignToggle();
- }
- else if (whichButton === 'preferences') {
- if ($(el).attr('data-prefs-popup') === 'menu') {
- this.handlePrefsClick();
- }
- else {
- this.closePopups();
- prefsPopup = $(el).attr('data-prefs-popup');
- if (prefsPopup === 'keyboard') {
- this.keyboardPrefsDialog.show();
- }
- else if (prefsPopup === 'captions') {
- this.captionPrefsDialog.show();
- }
- else if (prefsPopup === 'descriptions') {
- this.descPrefsDialog.show();
- }
- else if (prefsPopup === 'transcript') {
- this.transcriptPrefsDialog.show();
- }
- }
- }
- else if (whichButton === 'help') {
- this.handleHelpClick();
- }
- else if (whichButton === 'transcript') {
- this.handleTranscriptToggle();
- }
- else if (whichButton === 'fullscreen') {
- this.clickedFullscreenButton = true;
- this.handleFullscreenToggle();
- }
- };
-
- AblePlayer.prototype.okToHandleKeyPress = function () {
-
- // returns true unless user's focus is on a UI element
- // that is likely to need supported keystrokes, including space
-
- var activeElement = AblePlayer.getActiveDOMElement();
-
- if ($(activeElement).prop('tagName') === 'INPUT') {
- return false;
- }
- else {
- return true;
- }
- };
-
- AblePlayer.prototype.onPlayerKeyPress = function (e) {
-
- // handle keystrokes (using DHTML Style Guide recommended key combinations)
- // https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer
- // Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences
- // NOTE #1: Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down
- // The rest are reasonable best choices
- // NOTE #2: If there are multiple players on a single page, keystroke handlers
- // are only bound to the FIRST player
- // NOTE #3: The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions
- // including removal of the "media player" design pattern. There's an issue about that:
- // https://github.com/w3c/aria-practices/issues/27
-
- if (!this.okToHandleKeyPress()) {
- return false;
- }
- // Convert to lower case.
- var which = e.which;
- if (which >= 65 && which <= 90) {
- which += 32;
- }
- // Only use keypress to control player if focus is NOT on a form field or contenteditable element
- if (!(
- $(':focus').is('[contenteditable]') ||
- $(':focus').is('input') ||
- $(':focus').is('textarea') ||
- $(':focus').is('select') ||
- e.target.hasAttribute('contenteditable') ||
- e.target.tagName === 'INPUT' ||
- e.target.tagName === 'TEXTAREA' ||
- e.target.tagName === 'SELECT'
- )){
-
- if (which === 27) { // escape
- this.closePopups();
- }
- else if (which === 32) { // spacebar = play/pause
- if (this.$ableWrapper.find('.able-controller button:focus').length === 0) {
- // only toggle play if a button does not have focus
- // if a button has focus, space should activate that button
- this.clickedPlay = true; // important to set this var for program control
- this.handlePlay();
- }
- }
- else if (which === 112) { // p = play/pause
- if (this.usingModifierKeys(e)) {
- this.handlePlay();
- }
- }
- else if (which === 115) { // s = stop (now restart)
- if (this.usingModifierKeys(e)) {
- this.handleRestart();
- }
- }
- else if (which === 109) { // m = mute
- if (this.usingModifierKeys(e)) {
- this.handleMute();
- }
- }
- else if (which === 118) { // v = volume
- if (this.usingModifierKeys(e)) {
- this.handleVolume();
- }
- }
- else if (which >= 49 && which <= 57) { // set volume 1-9
- if (this.usingModifierKeys(e)) {
- this.handleVolume(which);
- }
- }
- else if (which === 99) { // c = caption toggle
- if (this.usingModifierKeys(e)) {
- this.handleCaptionToggle();
- }
- }
- else if (which === 100) { // d = description
- if (this.usingModifierKeys(e)) {
- this.handleDescriptionToggle();
- }
- }
- else if (which === 102) { // f = forward
- if (this.usingModifierKeys(e)) {
- this.handleFastForward();
- }
- }
- else if (which === 114) { // r = rewind
- if (this.usingModifierKeys(e)) {
- this.handleRewind();
- }
- }
- else if (which === 98) { // b = back (previous track)
- if (this.usingModifierKeys(e)) {
- this.handlePrevTrack();
- }
- }
- else if (which === 110) { // n = next track
- if (this.usingModifierKeys(e)) {
- this.handleNextTrack();
- }
- }
- else if (which === 101) { // e = preferences
- if (this.usingModifierKeys(e)) {
- this.handlePrefsClick();
- }
- }
- else if (which === 13) { // Enter
- var thisElement = $(document.activeElement);
- if (thisElement.prop('tagName') === 'SPAN') {
- // register a click on this SPAN
- // if it's a transcript span the transcript span click handler will take over
- thisElement.click();
- }
- else if (thisElement.prop('tagName') === 'LI') {
- thisElement.click();
- }
- }
- }
- };
-
- AblePlayer.prototype.addHtml5MediaListeners = function () {
-
- var thisObj = this;
-
- // NOTE: iOS and some browsers do not support autoplay
- // and no events are triggered until media begins to play
- // Able Player gets around this by automatically loading media in some circumstances
- // (see initialize.js > initPlayer() for details)
- this.$media
- .on('emptied',function() {
- // do something
- })
- .on('loadedmetadata',function() {
- // should be able to get duration now
- thisObj.duration = thisObj.media.duration;
- thisObj.onMediaNewSourceLoad();
- })
- .on('canplay',function() {
- // previously handled seeking to startTime here
- // but it's probably safer to wait for canplaythrough
- // so we know player can seek ahead to anything
- })
- .on('canplaythrough',function() {
- if (thisObj.userClickedPlaylist) {
- if (!thisObj.startedPlaying) {
- // start playing; no further user action is required
- thisObj.playMedia();
- }
- thisObj.userClickedPlaylist = false; // reset
- }
- if (thisObj.seekTrigger == 'restart' || thisObj.seekTrigger == 'chapter' || thisObj.seekTrigger == 'transcript') {
- // by clicking on any of these elements, user is likely intending to play
- // Not included: elements where user might click multiple times in succession
- // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play
- thisObj.playMedia();
- }
- else if (!thisObj.startedPlaying) {
- if (thisObj.startTime > 0) {
- if (thisObj.seeking) {
- // a seek has already been initiated
- // since canplaythrough has been triggered, the seek is complete
- thisObj.seeking = false;
- if (thisObj.autoplay || thisObj.okToPlay) {
- thisObj.playMedia();
- }
- }
- else {
- // haven't started seeking yet
- thisObj.seekTo(thisObj.startTime);
- }
- }
- else if (thisObj.defaultChapter && typeof thisObj.selectedChapters !== 'undefined') {
- thisObj.seekToChapter(thisObj.defaultChapter);
- }
- else {
- // there is no startTime, therefore no seeking required
- if (thisObj.autoplay || thisObj.okToPlay) {
- thisObj.playMedia();
- }
- }
- }
- else if (thisObj.hasPlaylist) {
- if ((thisObj.playlistIndex !== thisObj.$playlist.length) || thisObj.loop) {
- // this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
- thisObj.playMedia();
- }
- }
- else {
- // already started playing
- // we're here because a new media source has been loaded and is ready to resume playback
- thisObj.getPlayerState().then(function(currentState) {
- if (thisObj.swappingSrc && (currentState === 'stopped' || currentState === 'paused')) {
- thisObj.startedPlaying = false;
- if (thisObj.swapTime > 0) {
- thisObj.seekTo(thisObj.swapTime);
- }
- else {
- thisObj.playMedia();
- }
- }
- });
- }
- })
- .on('play',function() {
- // both 'play' and 'playing' seem to be fired in all browsers (including IE11)
- // therefore, doing nothing here & doing everything when 'playing' is triggered
- thisObj.refreshControls('playpause');
- })
- .on('playing',function() {
- thisObj.playing = true;
- thisObj.paused = false;
- thisObj.refreshControls('playpause');
- })
- .on('ended',function() {
- thisObj.playing = false;
- thisObj.paused = true;
- thisObj.onMediaComplete();
- })
- .on('progress', function() {
- thisObj.refreshControls('timeline');
- })
- .on('waiting',function() {
- // do something
- // previously called refreshControls() here but this event probably doesn't warrant a refresh
- })
- .on('durationchange',function() {
- // Display new duration.
- thisObj.refreshControls('timeline');
- })
- .on('timeupdate',function() {
- thisObj.onMediaUpdateTime(); // includes a call to refreshControls()
- })
- .on('pause',function() {
- if (!thisObj.clickedPlay) {
- // 'pause' was triggered automatically, not initiated by user
- // this happens in some browsers when swapping source
- // (e.g., between tracks in a playlist or swapping description)
- if (thisObj.hasPlaylist || thisObj.swappingSrc) {
- // do NOT set playing to false.
- // doing so prevents continual playback after new track is loaded
- }
- else {
- thisObj.playing = false;
- thisObj.paused = true;
- }
- }
- else {
- thisObj.playing = false;
- thisObj.paused = true;
- }
- thisObj.clickedPlay = false; // done with this variable
- thisObj.onMediaPause(); // includes a call to refreshControls()
- })
- .on('ratechange',function() {
- // do something
- })
- .on('volumechange',function() {
- thisObj.volume = thisObj.getVolume();
- if (thisObj.debug) {
-
- }
- })
- .on('error',function() {
- if (thisObj.debug) {
- switch (thisObj.media.error.code) {
- case 1:
-
- break;
- case 2:
-
- break;
- case 3:
-
- break;
- case 4:
-
- break;
- }
- }
- });
- };
-
- AblePlayer.prototype.addVimeoListeners = function () {
-
-// The following content is orphaned. It was in 'canplaythrough' but there's no equivalent event in Vimeo.
-// Maybe it should go under 'loaded' or 'progress' ???
-/*
- if (thisObj.userClickedPlaylist) {
- if (!thisObj.startedPlaying) {
- // start playing; no further user action is required
- thisObj.playMedia();
- }
- thisObj.userClickedPlaylist = false; // reset
- }
- if (thisObj.seekTrigger == 'restart' || thisObj.seekTrigger == 'chapter' || thisObj.seekTrigger == 'transcript') {
- // by clicking on any of these elements, user is likely intending to play
- // Not included: elements where user might click multiple times in succession
- // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play
- thisObj.playMedia();
- }
- else if (!thisObj.startedPlaying) {
- if (thisObj.startTime > 0) {
- if (thisObj.seeking) {
- // a seek has already been initiated
- // since canplaythrough has been triggered, the seek is complete
- thisObj.seeking = false;
- if (thisObj.autoplay || thisObj.okToPlay) {
- thisObj.playMedia();
- }
- }
- else {
- // haven't started seeking yet
- thisObj.seekTo(thisObj.startTime);
- }
- }
- else if (thisObj.defaultChapter && typeof thisObj.selectedChapters !== 'undefined') {
- thisObj.seekToChapter(thisObj.defaultChapter);
- }
- else {
- // there is no startTime, therefore no seeking required
- if (thisObj.autoplay || thisObj.okToPlay) {
- thisObj.playMedia();
- }
- }
- }
- else if (thisObj.hasPlaylist) {
- if ((thisObj.playlistIndex !== thisObj.$playlist.length) || thisObj.loop) {
- // this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
- thisObj.playMedia();
- }
- }
- else {
- // already started playing
- // we're here because a new media source has been loaded and is ready to resume playback
- thisObj.getPlayerState().then(function(currentState) {
- if (thisObj.swappingSrc && currentState === 'stopped') {
- // Safari is the only browser that returns value of 'stopped' (observed in 12.0.1 on MacOS)
- // This prevents 'timeupdate' events from triggering, which prevents the new media src
- // from resuming playback at swapTime
- // This is a hack to jump start Safari
- thisObj.startedPlaying = false;
- if (thisObj.swapTime > 0) {
- thisObj.seekTo(thisObj.swapTime);
- }
- else {
- thisObj.playMedia();
- }
- }
- });
- }
-
-*/
-
- var thisObj = this;
-
- // Vimeo doesn't seem to support chaining of on() functions
- // so each event listener must be attached separately
- this.vimeoPlayer.on('loaded', function(vimeoId) {
- // Triggered when a new video is loaded in the player
- thisObj.onMediaNewSourceLoad();
- });
- this.vimeoPlayer.on('play', function(data) {
- // Triggered when the video plays
- thisObj.playing = true;
- thisObj.startedPlaying = true;
- thisObj.paused = false;
- thisObj.refreshControls('playpause');
- });
- this.vimeoPlayer.on('ended', function(data) {
- // Triggered any time the video playback reaches the end.
- // Note: when loop is turned on, the ended event will not fire.
- thisObj.playing = false;
- thisObj.paused = true;
- thisObj.onMediaComplete();
- });
- this.vimeoPlayer.on('bufferstart', function() {
- // Triggered when buffering starts in the player.
- // This is also triggered during preload and while seeking.
- // There is no associated data with this event.
- });
- this.vimeoPlayer.on('bufferend', function() {
- // Triggered when buffering ends in the player.
- // This is also triggered at the end of preload and seeking.
- // There is no associated data with this event.
- });
- this.vimeoPlayer.on('progress', function(data) {
- // Triggered as the video is loaded.
- // Reports back the amount of the video that has been buffered (NOT the amount played)
- // Data has keys duration, percent, and seconds
- });
- this.vimeoPlayer.on('seeking', function(data) {
- // Triggered when the player starts seeking to a specific time.
- // A timeupdate event will also be fired at the same time.
- });
- this.vimeoPlayer.on('seeked', function(data) {
- // Triggered when the player seeks to a specific time.
- // A timeupdate event will also be fired at the same time.
- });
- this.vimeoPlayer.on('timeupdate',function(data) {
- // Triggered as the currentTime of the video updates.
- // It generally fires every 250ms, but it may vary depending on the browser.
- thisObj.onMediaUpdateTime(data['duration'], data['seconds']);
- });
- this.vimeoPlayer.on('pause',function(data) {
- // Triggered when the video pauses
- if (!thisObj.clickedPlay) {
- // 'pause' was triggered automatically, not initiated by user
- // this happens in some browsers (not Chrome, as of 70.x)
- // when swapping source (e.g., between tracks in a playlist, or swapping description)
- if (thisObj.hasPlaylist || thisObj.swappingSrc) {
- // do NOT set playing to false.
- // doing so prevents continual playback after new track is loaded
- }
- else {
- thisObj.playing = false;
- thisObj.paused = true;
- }
- }
- else {
- thisObj.playing = false;
- thisObj.paused = true;
- }
- thisObj.clickedPlay = false; // done with this variable
- thisObj.onMediaPause();
- thisObj.refreshControls('playpause');
- });
- this.vimeoPlayer.on('playbackratechange',function(data) {
- // Triggered when the playback rate of the video in the player changes.
- // The ability to change rate can be disabled by the creator
- // and the event will not fire for those videos.
- // data contains one key: 'playbackRate'
- thisObj.vimeoPlaybackRate = data['playbackRate'];
- });
- this.vimeoPlayer.on('texttrackchange', function(data) {
- // Triggered when the active text track (captions/subtitles) changes.
- // The values will be null if text tracks are turned off.
- // data contains three keys: kind, label, language
- });
- this.vimeoPlayer.on('volumechange',function(data) {
- // Triggered when the volume in the player changes.
- // Some devices do not support setting the volume of the video
- // independently from the system volume,
- // so this event will never fire on those devices.
- thisObj.volume = data['volume'] * 10;
- });
- this.vimeoPlayer.on('error',function(data) {
- // do something with the available data
- // data contains three keys: message, method, name
- // message: A user-friendly error message
- // method: The Vimeo API method call that triggered the error
- // name: Name of the error (not necesssarily user-friendly)
- });
- };
-
- AblePlayer.prototype.addEventListeners = function () {
-
- var thisObj, whichButton, thisElement;
-
- // Save the current object context in thisObj for use with inner functions.
- thisObj = this;
-
- // Appropriately resize media player for full screen.
- $(window).resize(function () {
- thisObj.onWindowResize();
- });
-
- // Refresh player if it changes from hidden to visible
- // There is no event triggered by a change in visibility
- // but MutationObserver works in most browsers (but NOT in IE 10 or earlier)
- // http://caniuse.com/#feat=mutationobserver
- if (window.MutationObserver) {
- var target = this.$ableDiv[0];
- var observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
- // the player's style attribute has changed. Check to see if it's visible
- if (thisObj.$ableDiv.is(':visible')) {
- thisObj.refreshControls('init');
- }
- }
- });
- });
- var config = { attributes: true, childList: true, characterData: true };
- observer.observe(target, config);
- }
- else {
- // browser doesn't support MutationObserver
- // TODO: Figure out an alternative solution for this rare use case in older browsers
- // See example in buildplayer.js > useSvg()
- }
- if (typeof this.seekBar !== 'undefined') {
- this.addSeekbarListeners();
- }
- else {
- // wait a bit and try again
- // TODO: Should set this up to keep trying repeatedly.
- // Seekbar listeners are critical.
- setTimeout(function() {
- if (typeof thisObj.seekBar !== 'undefined') {
- thisObj.addSeekbarListeners();
- }
- },2000);
- }
-
- // handle clicks on player buttons
- this.$controllerDiv.find('button').on('click',function(e){
- e.stopPropagation();
- thisObj.onClickPlayerButton(this);
- });
-
- // handle clicks (left only) anywhere on the page. If any popups are open, close them.
- $(document).on('click',function(e) {
-
- if (e.button !== 0) { // not a left click
- return false;
- }
- if ($('.able-popup:visible').length || $('.able-volume-popup:visible')) {
- // at least one popup is visible
- thisObj.closePopups();
- }
- });
-
- // handle mouse movement over player; make controls visible again if hidden
- this.$ableDiv.on('mousemove',function() {
- if (thisObj.controlsHidden) {
- thisObj.fadeControls('in');
- thisObj.controlsHidden = false;
- // if there's already an active timeout, clear it and start timer again
- if (thisObj.hideControlsTimeoutStatus === 'active') {
- window.clearTimeout(thisObj.hideControlsTimeout);
- thisObj.hideControlsTimeoutStatus = 'clear';
- }
- if (thisObj.hideControls) {
- // after showing controls, hide them again after a brief timeout
- thisObj.invokeHideControlsTimeout();
- }
- }
- else {
- // if there's already an active timeout, clear it and start timer again
- if (thisObj.hideControlsTimeoutStatus === 'active') {
- window.clearTimeout(thisObj.hideControlsTimeout);
- thisObj.hideControlsTimeoutStatus = 'clear';
- if (thisObj.hideControls) {
- thisObj.invokeHideControlsTimeout();
- }
- }
- }
- });
-
- // if user presses a key from anywhere on the page, show player controls
- $(document).keydown(function() {
- if (thisObj.controlsHidden) {
- thisObj.fadeControls('in');
- thisObj.controlsHidden = false;
- if (thisObj.hideControlsTimeoutStatus === 'active') {
- window.clearTimeout(thisObj.hideControlsTimeout);
- thisObj.hideControlsTimeoutStatus = 'clear';
- }
- if (thisObj.hideControls) {
- // after showing controls, hide them again after a brief timeout
- thisObj.invokeHideControlsTimeout();
- }
- }
- else {
- // controls are visible
- // if there's already an active timeout, clear it and start timer again
- if (thisObj.hideControlsTimeoutStatus === 'active') {
- window.clearTimeout(thisObj.hideControlsTimeout);
- thisObj.hideControlsTimeoutStatus = 'clear';
-
- if (thisObj.hideControls) {
- thisObj.invokeHideControlsTimeout();
- }
- }
- }
- });
-
- // handle local keydown events if this isn't the only player on the page;
- // otherwise these are dispatched by global handler (see ableplayer-base,js)
- this.$ableDiv.keydown(function (e) {
- if (AblePlayer.nextIndex > 1) {
- thisObj.onPlayerKeyPress(e);
- }
- });
-
- // transcript is not a child of this.$ableDiv
- // therefore, must be added separately
- if (this.$transcriptArea) {
- this.$transcriptArea.keydown(function (e) {
- if (AblePlayer.nextIndex > 1) {
- thisObj.onPlayerKeyPress(e);
- }
- });
- }
-
- // handle clicks on playlist items
- if (this.$playlist) {
- this.$playlist.click(function(e) {
- if (!thisObj.userClickedPlaylist) {
- // stopgap in case multiple clicks are fired on the same playlist item
- thisObj.userClickedPlaylist = true; // will be set to false after new src is loaded & canplaythrough is triggered
- thisObj.playlistIndex = $(this).index();
- thisObj.cuePlaylistItem(thisObj.playlistIndex);
- }
- });
- }
-
- // Also play/pause when clicking on the media.
- this.$media.click(function () {
- thisObj.handlePlay();
- });
-
- // add listeners for media events
- if (this.player === 'html5') {
- this.addHtml5MediaListeners();
- }
- else if (this.player === 'vimeo') {
- this.addVimeoListeners();
- }
- else if (this.player === 'youtube') {
- // Youtube doesn't give us time update events, so we just periodically generate them ourselves
- setInterval(function () {
- thisObj.onMediaUpdateTime();
- }, 300);
- }
- };
-})(jQuery);
-
-(function ($) {
-
- AblePlayer.prototype.initDragDrop = function ( which ) {
-
- // supported values of which: 'sign', 'transcript'
-
- // NOTE: "Drag and Drop" for Able Player is a metaphor only!!!
- // HTML5 Drag & Drop API enables moving elements to new locations in the DOM
- // Thats not our purpose; we're simply changing the visible position on-screen
- // Therefore, the drag & drop interface was overhauled in v2.3.41 to simple
- // use mouse (and keyboard) events to change CSS positioning properties
-
- // There are nevertheless lessons to be learned from Drag & Drop about accessibility:
- // http://dev.opera.com/articles/accessible-drag-and-drop/
-
- var thisObj, $window, $toolbar, windowName, $resizeHandle, resizeZIndex;
-
- thisObj = this;
-
- if (which === 'transcript') {
- $window = this.$transcriptArea;
- windowName = 'transcript-window';
- $toolbar = this.$transcriptToolbar;
- }
- else if (which === 'sign') {
- $window = this.$signWindow;
- windowName = 'sign-window';
- $toolbar = this.$signToolbar;
- }
-
- // add class to trigger change in cursor on hover
- $toolbar.addClass('able-draggable');
-
- // add resize handle selector to bottom right corner
- $resizeHandle = $('',{
- 'class': 'able-resizable'
- });
- // assign z-index that's slightly higher than parent window
- resizeZIndex = parseInt($window.css('z-index')) + 100;
- $resizeHandle.css('z-index',resizeZIndex);
- $window.append($resizeHandle);
-
- // add event listener to toolbar to start and end drag
- // other event listeners will be added when drag starts
- $toolbar.on('mousedown mouseup touchstart touchend', function(e) {
- e.stopPropagation();
- if (e.type === 'mousedown' || e.type === 'touchstart') {
- if (!thisObj.windowMenuClickRegistered) {
- thisObj.windowMenuClickRegistered = true;
- thisObj.startMouseX = e.pageX;
- thisObj.startMouseY = e.pageY;
- thisObj.dragDevice = 'mouse'; // ok to use this even if device is a touchpad
- thisObj.startDrag(which, $window);
- }
- }
- else if (e.type === 'mouseup' || e.type === 'touchend') {
- if (thisObj.dragging && thisObj.dragDevice === 'mouse') {
- thisObj.endDrag(which);
- }
- }
- return false;
- });
-
- // add event listeners for resizing
- $resizeHandle.on('mousedown mouseup touchstart touchend', function(e) {
- e.stopPropagation();
- if (e.type === 'mousedown' || e.type === 'touchstart') {
- if (!thisObj.windowMenuClickRegistered) {
- thisObj.windowMenuClickRegistered = true;
- thisObj.startMouseX = e.pageX;
- thisObj.startMouseY = e.pageY;
- thisObj.startResize(which, $window);
- }
- }
- else if (e.type === 'mouseup' || e.type === 'touchend') {
- if (thisObj.resizing) {
- thisObj.endResize(which);
- }
- }
- return false;
- });
-
- // whenever a window is clicked, bring it to the foreground
- $window.on('click', function() {
-
- if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) {
- thisObj.updateZIndex(which);
- }
- thisObj.finishingDrag = false;
- });
-
- this.addWindowMenu(which,$window,windowName);
- };
-
- AblePlayer.prototype.addWindowMenu = function(which, $window, windowName) {
-
-
- var thisObj, $windowAlert, menuId, $newButton, $buttonIcon, buttonImgSrc, $buttonImg,
- $buttonLabel, tooltipId, $tooltip, $popup,
- label, position, buttonHeight, buttonWidth, tooltipY, tooltipX, tooltipStyle, tooltip,
- $optionList, menuBaseId, options, i, $optionItem, option, menuId;
-
- thisObj = this;
-
- // Add a Boolean that will be set to true temporarily if window button or a menu item is clicked
- // This will prevent the click event from also triggering a mousedown event on the toolbar
- // (which would unexpectedly send the window into drag mode)
- this.windowMenuClickRegistered = false;
-
- // Add another Boolean that will be set to true temporarily when mouseup fires at the end of a drag
- // this will prevent the click event from being triggered
- this.finishingDrag = false;
-
- // create an alert div and add it to window
- $windowAlert = $('
');
- $windowAlert.addClass('able-alert');
- $windowAlert.hide();
- $windowAlert.appendTo(this.$activeWindow);
- $windowAlert.css({
- top: $window.offset().top
- });
-
- // add button to draggable window which triggers a popup menu
- // for now, re-use preferences icon for this purpose
- menuId = this.mediaId + '-' + windowName + '-menu';
- $newButton = $('
',{
- 'type': 'button',
- 'tabindex': '0',
- 'aria-label': this.tt.windowButtonLabel,
- 'aria-haspopup': 'true',
- 'aria-controls': menuId,
- 'class': 'able-button-handler-preferences'
- });
- if (this.iconType === 'font') {
- $buttonIcon = $('',{
- 'class': 'icon-preferences',
- 'aria-hidden': 'true'
- });
- $newButton.append($buttonIcon);
- }
- else {
- // use image
- buttonImgSrc = this.rootPath + 'button-icons/' + this.toolbarIconColor + '/preferences.png';
- $buttonImg = $(' ',{
- 'src': buttonImgSrc,
- 'alt': '',
- 'role': 'presentation'
- });
- $newButton.append($buttonImg);
- }
-
- // add the visibly-hidden label for screen readers that don't support aria-label on the button
- $buttonLabel = $('',{
- 'class': 'able-clipped'
- }).text(this.tt.windowButtonLabel);
- $newButton.append($buttonLabel);
-
- // add a tooltip that displays aria-label on mouseenter or focus
- tooltipId = this.mediaId + '-' + windowName + '-tooltip';
- $tooltip = $('',{
- 'class' : 'able-tooltip',
- 'id' : tooltipId
- }).hide();
- $newButton.on('mouseenter focus',function(e) {
- var label = $(this).attr('aria-label');
- // get position of this button
- var position = $(this).position();
- var buttonHeight = $(this).height();
- var buttonWidth = $(this).width();
- var tooltipY = position.top - buttonHeight - 5;
- var tooltipX = 0;
- var tooltipStyle = {
- left: '',
- right: tooltipX + 'px',
- top: tooltipY + 'px'
- };
- var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label).css(tooltipStyle);
- thisObj.showTooltip(tooltip);
- $(this).on('mouseleave blur',function() {
- AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
- });
- });
-
- // setup popup menu
- $popup = this.setupPopups(windowName); // 'transcript-window' or 'sign-window'
- // define vars and assemble all the parts
- if (which === 'transcript') {
- this.$transcriptAlert = $windowAlert;
- this.$transcriptPopupButton = $newButton;
- this.$transcriptPopup = $popup;
- this.$transcriptToolbar.append($windowAlert,$newButton,$tooltip,$popup);
- }
- else if (which === 'sign') {
- this.$signAlert = $windowAlert;
- this.$signPopupButton = $newButton;
- this.$signPopup = $popup;
- this.$signToolbar.append($windowAlert,$newButton,$tooltip,$popup);
- }
-
- // handle button click
- $newButton.on('click mousedown keydown',function(e) {
- e.stopPropagation();
- if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) {
- // don't set windowMenuClickRegistered yet; that happens in handler function
- thisObj.handleWindowButtonClick(which, e);
- }
- thisObj.finishingDrag = false;
- });
-
- this.addResizeDialog(which, $window);
- };
-
- AblePlayer.prototype.addResizeDialog = function (which, $window) {
-
- var thisObj, $windowPopup, $windowButton,
- widthId, heightId, startingWidth, startingHeight, aspectRatio,
- $resizeForm, $resizeWrapper,
- $resizeWidthDiv, $resizeWidthInput, $resizeWidthLabel,
- $resizeHeightDiv, $resizeHeightInput, $resizeHeightLabel,
- tempWidth, tempHeight,
- $saveButton, $cancelButton, newWidth, newHeight, resizeDialog;
-
- thisObj = this;
-
- if (which === 'transcript') {
- $windowPopup = this.$transcriptPopup;
- $windowButton = this.$transcriptPopupButton;
- }
- else if (which === 'sign') {
- $windowPopup = this.$signPopup;
- $windowButton = this.$signPopupButton;
- }
-
- widthId = this.mediaId + '-resize-' + which + '-width';
- heightId = this.mediaId + '-resize-' + which + '-height';
- startingWidth = $window.width();
- startingHeight = $window.height();
- aspectRatio = startingWidth / startingHeight;
-
- $resizeForm = $('
',{
- 'class' : 'able-resize-form'
- });
-
- // inner container for all content, will be assigned to modal div's aria-describedby
- $resizeWrapper = $('
');
-
- // width field
- $resizeWidthDiv = $('
');
- $resizeWidthInput = $('
',{
- 'type': 'text',
- 'id': widthId,
- 'value': startingWidth
- });
- $resizeWidthLabel = $('
',{
- 'for': widthId
- }).text(this.tt.width);
-
- // height field
- $resizeHeightDiv = $('
');
- $resizeHeightInput = $(' ',{
- 'type': 'text',
- 'id': heightId,
- 'value': startingHeight
- });
- $resizeHeightLabel = $('',{
- 'for': heightId
- }).text(this.tt.height);
-
- if (which === 'sign') {
- // make height a read-only field
- // and calculate its value based on width to preserve aspect ratio
- $resizeHeightInput.prop('readonly',true);
- $resizeWidthInput.on('input',function() {
- tempWidth = $(this).val();
- tempHeight = Math.round(tempWidth/aspectRatio, 0);
- $resizeHeightInput.val(tempHeight);
- })
- }
-
- // Add save and cancel buttons.
- $saveButton = $('' + this.tt.save + ' ');
- $cancelButton = $('' + this.tt.cancel + ' ');
- $saveButton.on('click',function () {
- newWidth = $('#' + widthId).val();
- newHeight = $('#' + heightId).val();
- if (newWidth !== startingWidth || newHeight !== startingHeight) {
- thisObj.resizeObject(which,newWidth,newHeight);
- thisObj.updateCookie(which);
- }
- resizeDialog.hide();
- $windowPopup.hide();
- $windowButton.focus();
- });
- $cancelButton.on('click',function () {
- resizeDialog.hide();
- $windowPopup.hide();
- $windowButton.focus();
- });
-
- // Now assemble all the parts
- $resizeWidthDiv.append($resizeWidthLabel,$resizeWidthInput);
- $resizeHeightDiv.append($resizeHeightLabel,$resizeHeightInput);
- $resizeWrapper.append($resizeWidthDiv,$resizeHeightDiv);
- $resizeForm.append($resizeWrapper,' ',$saveButton,$cancelButton);
-
- // must be appended to the BODY!
- // otherwise when aria-hidden="true" is applied to all background content
- // that will include an ancestor of the dialog,
- // which will render the dialog unreadable by screen readers
- $('body').append($resizeForm);
- resizeDialog = new AccessibleDialog($resizeForm, $windowButton, 'alert', this.tt.windowResizeHeading, $resizeWrapper, this.tt.closeButtonLabel, '20em');
- if (which === 'transcript') {
- this.transcriptResizeDialog = resizeDialog;
- }
- else if (which === 'sign') {
- this.signResizeDialog = resizeDialog;
- }
- };
-
- AblePlayer.prototype.handleWindowButtonClick = function (which, e) {
-
- var thisObj, $windowPopup, $windowButton, $toolbar, popupTop;
-
- thisObj = this;
-
- if (which === 'transcript') {
- $windowPopup = this.$transcriptPopup;
- $windowButton = this.$transcriptPopupButton;
- $toolbar = this.$transcriptToolbar;
- }
- else if (which === 'sign') {
- $windowPopup = this.$signPopup;
- $windowButton = this.$signPopupButton;
- $toolbar = this.$signToolbar;
- }
-
- if (e.type === 'keydown') {
- // user pressed a key
- if (e.which === 32 || e.which === 13) {
- // this was Enter or space
- this.windowMenuClickRegistered = true;
- }
- else if (e.which === 27) { // escape
- // hide the popup menu
- $windowPopup.hide('fast', function() {
- // also reset the Boolean
- thisObj.windowMenuClickRegistered = false;
- // also restore menu items to their original state
- $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
- // also return focus to window options button
- $windowButton.focus();
- });
- }
- else {
- return false;
- }
- }
- else {
- // this was a mouse event
- this.windowMenuClickRegistered = true;
- }
-
- if ($windowPopup.is(':visible')) {
- $windowPopup.hide(200,'',function() {
- thisObj.windowMenuClickRegistered = false; // reset
- });
- $windowPopup.find('li').removeClass('able-focus');
- $windowButton.attr('aria-expanded','false').focus();
- }
- else {
- // first, be sure window is on top
- this.updateZIndex(which);
- popupTop = $windowButton.position().top + $windowButton.outerHeight();
- $windowPopup.css('top', popupTop);
- $windowPopup.show(200,'',function() {
- $windowButton.attr('aria-expanded','true');
- $(this).find('li').first().focus().addClass('able-focus');
- thisObj.windowMenuClickRegistered = false; // reset
- });
- }
- };
-
- AblePlayer.prototype.handleMenuChoice = function (which, choice, e) {
-
- var thisObj, $window, $windowPopup, $windowButton, resizeDialog, $thisRadio;
-
- thisObj = this;
-
- if (which === 'transcript') {
- $window = this.$transcriptArea;
- $windowPopup = this.$transcriptPopup;
- $windowButton = this.$transcriptPopupButton;
- resizeDialog = this.transcriptResizeDialog;
- }
- else if (which === 'sign') {
- $window = this.$signWindow;
- $windowPopup = this.$signPopup;
- $windowButton = this.$signPopupButton;
- resizeDialog = this.signResizeDialog;
- }
- if (e.type === 'keydown') {
- if (e.which === 27) { // escape
- // hide the popup menu
- $windowPopup.hide('fast', function() {
- // also reset the Boolean
- thisObj.windowMenuClickRegistered = false;
- // also restore menu items to their original state
- $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
- // also return focus to window options button
- $windowButton.focus();
- });
- return false;
- }
- else {
- // all other keys will be handled by upstream functions
- if (choice !== 'close') {
- this.$activeWindow = $window;
- }
- return false;
- }
- }
-
- // hide the popup menu
- $windowPopup.hide('fast', function() {
- // also reset the boolean
- thisObj.windowMenuClickRegistered = false;
- // also restore menu items to their original state
- $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
- });
- if (choice !== 'close') {
- $windowButton.focus();
- }
-
- if (choice === 'move') {
- if (!this.showedAlert(which)) {
- this.showAlert(this.tt.windowMoveAlert,which);
- if (which === 'transcript') {
- this.showedTranscriptAlert = true;
- }
- else if (which === 'sign') {
- this.showedSignAlert = true;
- }
- }
- if (e.type === 'keydown') {
- this.dragDevice = 'keyboard';
- }
- else {
- this.dragDevice = 'mouse';
- }
- this.startDrag(which, $window);
- $windowPopup.hide().parent().focus();
- }
- else if (choice == 'resize') {
- // resize through the menu uses a form, not drag
- resizeDialog.show();
- }
- else if (choice == 'close') {
- // close window, place focus on corresponding button on controller bar
- if (which === 'transcript') {
- this.handleTranscriptToggle();
- }
- else if (which === 'sign') {
- this.handleSignToggle();
- }
- }
- };
-
- AblePlayer.prototype.startDrag = function(which, $element) {
-
- var thisObj, $windowPopup, zIndex, startPos, newX, newY;
-
- thisObj = this;
-
- if (!this.$activeWindow) {
- this.$activeWindow = $element;
- }
- this.dragging = true;
-
- if (which === 'transcript') {
- $windowPopup = this.$transcriptPopup;
- }
- else if (which === 'sign') {
- $windowPopup = this.$signPopup;
- }
-
- if (!this.showedAlert(which)) {
- this.showAlert(this.tt.windowMoveAlert,which);
- if (which === 'transcript') {
- this.showedTranscriptAlert = true;
- }
- else if (which === 'sign') {
- this.showedSignAlert = true;
- }
- }
-
- // if window's popup menu is open, close it
- if ($windowPopup.is(':visible')) {
- $windowPopup.hide();
- }
-
- // be sure this window is on top
- this.updateZIndex(which);
-
- // get starting position of element
- startPos = this.$activeWindow.position();
- this.dragStartX = startPos.left;
- this.dragStartY = startPos.top;
-
- if (typeof this.startMouseX === 'undefined') {
- this.dragDevice = 'keyboard';
- this.dragKeyX = this.dragStartX;
- this.dragKeyY = this.dragStartY;
- // add stopgap to prevent the Enter that triggered startDrag() from also triggering dragEnd()
- this.startingDrag = true;
- }
- else {
- this.dragDevice = 'mouse';
- // get offset between mouse position and top left corner of draggable element
- this.dragOffsetX = this.startMouseX - this.dragStartX;
- this.dragOffsetY = this.startMouseY - this.dragStartY;
- }
-
- // prepare element for dragging
- this.$activeWindow.addClass('able-drag').css({
- 'position': 'absolute',
- 'top': this.dragStartY + 'px',
- 'left': this.dragStartX + 'px'
- }).focus();
-
- // add device-specific event listeners
- if (this.dragDevice === 'mouse') { // might also be a touchpad
- $(document).on('mousemove touchmove',function(e) {
- if (thisObj.dragging) {
- // calculate new top left based on current mouse position - offset
- newX = e.pageX - thisObj.dragOffsetX;
- newY = e.pageY - thisObj.dragOffsetY;
- thisObj.resetDraggedObject( newX, newY );
- }
- });
- }
- else if (this.dragDevice === 'keyboard') {
- this.$activeWindow.on('keydown',function(e) {
- if (thisObj.dragging) {
- thisObj.dragKeys(which, e);
- }
- });
- }
- return false;
- };
-
- AblePlayer.prototype.dragKeys = function(which, e) {
-
- var key, keySpeed;
-
- var thisObj = this;
-
- // stopgap to prevent firing on initial Enter or space
- // that selected "Move" from menu
- if (this.startingDrag) {
- this.startingDrag = false;
- return false;
- }
- key = e.which;
- keySpeed = 10; // pixels per keypress event
-
- switch (key) {
- case 37: // left
- case 63234:
- this.dragKeyX -= keySpeed;
- break;
- case 38: // up
- case 63232:
- this.dragKeyY -= keySpeed;
- break;
- case 39: // right
- case 63235:
- this.dragKeyX += keySpeed;
- break;
- case 40: // down
- case 63233:
- this.dragKeyY += keySpeed;
- break;
- case 13: // enter
- case 27: // escape
- this.endDrag(which);
- return false;
- default:
- return false;
- }
- this.resetDraggedObject(this.dragKeyX,this.dragKeyY);
- if (e.preventDefault) {
- e.preventDefault();
- }
- return false;
- };
-
- AblePlayer.prototype.resetDraggedObject = function ( x, y) {
- this.$activeWindow.css({
- 'left': x + 'px',
- 'top': y + 'px'
- });
- },
-
- AblePlayer.prototype.resizeObject = function ( which, width, height ) {
-
- var innerHeight;
-
- // which is either 'transcript' or 'sign'
- this.$activeWindow.css({
- 'width': width + 'px',
- 'height': height + 'px'
- });
-
- if (which === 'transcript') {
- // $activeWindow is the outer $transcriptArea
- // but the inner able-transcript also needs to be resized proporitionally
- // (it's 50px less than its outer container)
- innerHeight = height - 50;
- this.$transcriptDiv.css('height', innerHeight + 'px');
- }
- };
-
- AblePlayer.prototype.endDrag = function(which) {
-
- var $window, $windowPopup, $windowButton;
-
- if (which === 'transcript') {
- $windowPopup = this.$transcriptPopup;
- $windowButton = this.$transcriptPopupButton;
- }
- else if (which === 'sign') {
- $windowPopup = this.$signPopup;
- $windowButton = this.$signPopupButton;
- }
-
- $(document).off('mousemove mouseup touchmove touchup');
- this.$activeWindow.off('keydown').removeClass('able-drag');
-
- if (this.dragDevice === 'keyboard') {
- $windowButton.focus();
- }
- this.dragging = false;
-
- // save final position of dragged element
- this.updateCookie(which);
-
- // reset starting mouse positions
- this.startMouseX = undefined;
- this.startMouseY = undefined;
-
- // Boolean to stop stray events from firing
- this.windowMenuClickRegistered = false;
- this.finishingDrag = true; // will be reset after window click event
-
- // finishingDrag should e reset after window click event,
- // which is triggered automatically after mouseup
- // However, in case that's not reliable in some browsers
- // need to ensure this gets cancelled
- setTimeout(function() {
- this.finishingDrag = false;
- }, 100);
- };
-
- AblePlayer.prototype.isCloseToCorner = function($window, mouseX, mouseY) {
-
- // return true if mouse is close to bottom right corner (resize target)
- var tolerance, position, top, left, width, height, bottom, right;
-
- tolerance = 10; // number of pixels in both directions considered "close enough"
-
- // first, get position of element
- position = $window.offset();
- top = position.top;
- left = position.left;
- width = $window.width();
- height = $window.height();
- bottom = top + height;
- right = left + width;
- if ((Math.abs(bottom-mouseY) <= tolerance) && (Math.abs(right-mouseX) <= tolerance)) {
- return true;
- }
- return false;
- };
-
- AblePlayer.prototype.startResize = function(which, $element) {
-
- var thisObj, $windowPopup, zIndex, startPos, newWidth, newHeight;
-
- thisObj = this;
- this.$activeWindow = $element;
- this.resizing = true;
-
- if (which === 'transcript') {
- $windowPopup = this.$transcriptPopup;
- }
- else if (which === 'sign') {
- $windowPopup = this.$signPopup;
- }
-
- // if window's popup menu is open, close it & place focus on button (???)
- if ($windowPopup.is(':visible')) {
- $windowPopup.hide().parent().focus();
- }
-
- // get starting width and height
- startPos = this.$activeWindow.position();
- this.dragKeyX = this.dragStartX;
- this.dragKeyY = this.dragStartY;
- this.dragStartWidth = this.$activeWindow.width();
- this.dragStartHeight = this.$activeWindow.height();
-
- // add event listeners
- $(document).on('mousemove touchmove',function(e) {
- if (thisObj.resizing) {
- // calculate new width and height based on changes to mouse position
- newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX);
- newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY);
- thisObj.resizeObject( which, newWidth, newHeight );
- }
- });
- return false;
- };
-
- AblePlayer.prototype.endResize = function(which) {
-
- var $window, $windowPopup, $windowButton;
-
- if (which === 'transcript') {
- $windowPopup = this.$transcriptPopup;
- $windowButton = this.$transcriptPopupButton;
- }
- else if (which === 'sign') {
- $windowPopup = this.$signPopup;
- $windowButton = this.$signPopupButton;
- }
-
- $(document).off('mousemove mouseup touchmove touchup');
- this.$activeWindow.off('keydown');
-
- $windowButton.show().focus();
- this.resizing = false;
- this.$activeWindow.removeClass('able-resize');
-
- // save final width and height of dragged element
- this.updateCookie(which);
-
- // Booleans for preventing stray events
- this.windowMenuClickRegistered = false;
- this.finishingDrag = true;
-
- // finishingDrag should e reset after window click event,
- // which is triggered automatically after mouseup
- // However, in case that's not reliable in some browsers
- // need to ensure this gets cancelled
- setTimeout(function() {
- this.finishingDrag = false;
- }, 100);
- };
-
-})(jQuery);
-
-(function ($) {
- AblePlayer.prototype.initSignLanguage = function() {
-
- // Sign language is only currently supported in HTML5 player, not YouTube or Vimeo
- if (this.player === 'html5') {
- // check to see if there's a sign language video accompanying this video
- // check only the first source
- // If sign language is provided, it must be provided for all sources
- this.signFile = this.$sources.first().attr('data-sign-src');
- if (this.signFile) {
- if (this.isIOS()) {
- // IOS does not allow multiple videos to play simultaneously
- // Therefore, sign language as rendered by Able Player unfortunately won't work
- this.hasSignLanguage = false;
- if (this.debug) {
-
- }
- }
- else {
- if (this.debug) {
-
- }
- this.hasSignLanguage = true;
- this.injectSignPlayerCode();
- }
- }
- else {
- this.hasSignLanguage = false;
- }
- }
- else {
- this.hasSignLanguage = false;
- }
- };
-
- AblePlayer.prototype.injectSignPlayerCode = function() {
-
- // create and inject surrounding HTML structure
-
- var thisObj, signVideoId, signVideoWidth, i, signSrc, srcType, $signSource;
-
- thisObj = this;
-
- signVideoWidth = this.getDefaultWidth('sign');
-
- signVideoId = this.mediaId + '-sign';
- this.$signVideo = $('',{
- 'id' : signVideoId,
- 'tabindex' : '-1'
- });
- this.signVideo = this.$signVideo[0];
- // for each original , add a to the sign
- for (i=0; i < this.$sources.length; i++) {
- signSrc = this.$sources[i].getAttribute('data-sign-src');
- srcType = this.$sources[i].getAttribute('type');
- if (signSrc) {
- $signSource = $('',{
- 'src' : signSrc,
- 'type' : srcType
- });
- this.$signVideo.append($signSource);
- }
- else {
- // source is missing a sign language version
- // can't include sign language
- this.hasSignLanguage = false;
- break;
- }
- }
-
- this.$signWindow = $('',{
- 'class' : 'able-sign-window',
- 'tabindex': '-1'
- });
- this.$signToolbar = $('
',{
- 'class': 'able-window-toolbar able-' + this.toolbarIconColor + '-controls'
- });
-
- this.$signWindow.append(this.$signToolbar, this.$signVideo);
-
- this.$ableWrapper.append(this.$signWindow);
-
- // make it draggable
- this.initDragDrop('sign');
-
- if (this.prefSign === 1) {
- // sign window is on. Go ahead and position it and show it
- this.positionDraggableWindow('sign',this.getDefaultWidth('sign'));
- }
- else {
- this.$signWindow.hide();
- }
- };
-
-})(jQuery);
-
-(function ($) {
- // Look up ISO 639-1 language codes to be used as subtitle labels
- // In some instances "name" has been trunctation for readability
- // Sources:
- // http://stackoverflow.com/questions/3217492/list-of-language-codes-in-yaml-or-json/4900304#4900304
- // https://www.venea.net/web/culture_code
-
- var isoLangs = {
- "ab":{
- "name":"Abkhaz",
- "nativeName":"аҧсуа"
- },
- "aa":{
- "name":"Afar",
- "nativeName":"Afaraf"
- },
- "af":{
- "name":"Afrikaans",
- "nativeName":"Afrikaans"
- },
- "ak":{
- "name":"Akan",
- "nativeName":"Akan"
- },
- "sq":{
- "name":"Albanian",
- "nativeName":"Shqip"
- },
- "am":{
- "name":"Amharic",
- "nativeName":"አማርኛ"
- },
- "ar":{
- "name":"Arabic",
- "nativeName":"العربية"
- },
- "an":{
- "name":"Aragonese",
- "nativeName":"Aragonés"
- },
- "hy":{
- "name":"Armenian",
- "nativeName":"Հայերեն"
- },
- "as":{
- "name":"Assamese",
- "nativeName":"অসমীয়া"
- },
- "av":{
- "name":"Avaric",
- "nativeName":"авар мацӀ, магӀарул мацӀ"
- },
- "ae":{
- "name":"Avestan",
- "nativeName":"avesta"
- },
- "ay":{
- "name":"Aymara",
- "nativeName":"aymar aru"
- },
- "az":{
- "name":"Azerbaijani",
- "nativeName":"azərbaycan dili"
- },
- "bm":{
- "name":"Bambara",
- "nativeName":"bamanankan"
- },
- "ba":{
- "name":"Bashkir",
- "nativeName":"башҡорт теле"
- },
- "eu":{
- "name":"Basque",
- "nativeName":"euskara, euskera"
- },
- "be":{
- "name":"Belarusian",
- "nativeName":"Беларуская"
- },
- "bn":{
- "name":"Bengali",
- "nativeName":"বাংলা"
- },
- "bh":{
- "name":"Bihari",
- "nativeName":"भोजपुरी"
- },
- "bi":{
- "name":"Bislama",
- "nativeName":"Bislama"
- },
- "bs":{
- "name":"Bosnian",
- "nativeName":"bosanski jezik"
- },
- "br":{
- "name":"Breton",
- "nativeName":"brezhoneg"
- },
- "bg":{
- "name":"Bulgarian",
- "nativeName":"български език"
- },
- "my":{
- "name":"Burmese",
- "nativeName":"ဗမာစာ"
- },
- "ca":{
- "name":"Catalan",
- "nativeName":"Català"
- },
- "ch":{
- "name":"Chamorro",
- "nativeName":"Chamoru"
- },
- "ce":{
- "name":"Chechen",
- "nativeName":"нохчийн мотт"
- },
- "ny":{
- "name":"Chichewa",
- "nativeName":"chiCheŵa, chinyanja"
- },
- "zh":{
- "name":"Chinese",
- "nativeName":"中文 (Zhōngwén), 汉语, 漢語"
- },
- "cv":{
- "name":"Chuvash",
- "nativeName":"чӑваш чӗлхи"
- },
- "kw":{
- "name":"Cornish",
- "nativeName":"Kernewek"
- },
- "co":{
- "name":"Corsican",
- "nativeName":"corsu, lingua corsa"
- },
- "cr":{
- "name":"Cree",
- "nativeName":"ᓀᐦᐃᔭᐍᐏᐣ"
- },
- "hr":{
- "name":"Croatian",
- "nativeName":"hrvatski"
- },
- "cs":{
- "name":"Czech",
- "nativeName":"česky, čeština"
- },
- "da":{
- "name":"Danish",
- "nativeName":"dansk"
- },
- "dv":{
- "name":"Divehi",
- "nativeName":"ދިވެހި"
- },
- "nl":{
- "name":"Dutch",
- "nativeName":"Nederlands, Vlaams"
- },
- "en":{
- "name":"English",
- "nativeName":"English"
- },
- "eo":{
- "name":"Esperanto",
- "nativeName":"Esperanto"
- },
- "et":{
- "name":"Estonian",
- "nativeName":"eesti, eesti keel"
- },
- "ee":{
- "name":"Ewe",
- "nativeName":"Eʋegbe"
- },
- "fo":{
- "name":"Faroese",
- "nativeName":"føroyskt"
- },
- "fj":{
- "name":"Fijian",
- "nativeName":"vosa Vakaviti"
- },
- "fi":{
- "name":"Finnish",
- "nativeName":"suomi, suomen kieli"
- },
- "fr":{
- "name":"French",
- "nativeName":"français, langue française"
- },
- "ff":{
- "name":"Fula",
- "nativeName":"Fulfulde, Pulaar, Pular"
- },
- "gl":{
- "name":"Galician",
- "nativeName":"Galego"
- },
- "ka":{
- "name":"Georgian",
- "nativeName":"ქართული"
- },
- "de":{
- "name":"German",
- "nativeName":"Deutsch"
- },
- "el":{
- "name":"Greek",
- "nativeName":"Ελληνικά"
- },
- "gn":{
- "name":"Guaraní",
- "nativeName":"Avañeẽ"
- },
- "gu":{
- "name":"Gujarati",
- "nativeName":"ગુજરાતી"
- },
- "ht":{
- "name":"Haitian",
- "nativeName":"Kreyòl ayisyen"
- },
- "ha":{
- "name":"Hausa",
- "nativeName":"Hausa, هَوُسَ"
- },
- "he":{
- "name":"Hebrew",
- "nativeName":"עברית"
- },
- "hz":{
- "name":"Herero",
- "nativeName":"Otjiherero"
- },
- "hi":{
- "name":"Hindi",
- "nativeName":"हिन्दी, हिंदी"
- },
- "ho":{
- "name":"Hiri Motu",
- "nativeName":"Hiri Motu"
- },
- "hu":{
- "name":"Hungarian",
- "nativeName":"Magyar"
- },
- "ia":{
- "name":"Interlingua",
- "nativeName":"Interlingua"
- },
- "id":{
- "name":"Indonesian",
- "nativeName":"Bahasa Indonesia"
- },
- "ie":{
- "name":"Interlingue",
- "nativeName":"Originally called Occidental; then Interlingue after WWII"
- },
- "ga":{
- "name":"Irish",
- "nativeName":"Gaeilge"
- },
- "ig":{
- "name":"Igbo",
- "nativeName":"Asụsụ Igbo"
- },
- "ik":{
- "name":"Inupiaq",
- "nativeName":"Iñupiaq, Iñupiatun"
- },
- "io":{
- "name":"Ido",
- "nativeName":"Ido"
- },
- "is":{
- "name":"Icelandic",
- "nativeName":"Íslenska"
- },
- "it":{
- "name":"Italian",
- "nativeName":"Italiano"
- },
- "iu":{
- "name":"Inuktitut",
- "nativeName":"ᐃᓄᒃᑎᑐᑦ"
- },
- "ja":{
- "name":"Japanese",
- "nativeName":"日本語 (にほんご/にっぽんご)"
- },
- "jv":{
- "name":"Javanese",
- "nativeName":"basa Jawa"
- },
- "kl":{
- "name":"Kalaallisut",
- "nativeName":"kalaallisut, kalaallit oqaasii"
- },
- "kn":{
- "name":"Kannada",
- "nativeName":"ಕನ್ನಡ"
- },
- "kr":{
- "name":"Kanuri",
- "nativeName":"Kanuri"
- },
- "ks":{
- "name":"Kashmiri",
- "nativeName":"कश्मीरी, كشميري"
- },
- "kk":{
- "name":"Kazakh",
- "nativeName":"Қазақ тілі"
- },
- "km":{
- "name":"Khmer",
- "nativeName":"ភាសាខ្មែរ"
- },
- "ki":{
- "name":"Kikuyu",
- "nativeName":"Gĩkũyũ"
- },
- "rw":{
- "name":"Kinyarwanda",
- "nativeName":"Ikinyarwanda"
- },
- "ky":{
- "name":"Kyrgyz",
- "nativeName":"кыргыз тили"
- },
- "kv":{
- "name":"Komi",
- "nativeName":"коми кыв"
- },
- "kg":{
- "name":"Kongo",
- "nativeName":"KiKongo"
- },
- "ko":{
- "name":"Korean",
- "nativeName":"한국어 (韓國語), 조선말 (朝鮮語)"
- },
- "ku":{
- "name":"Kurdish",
- "nativeName":"Kurdî, كوردی"
- },
- "kj":{
- "name":"Kuanyama",
- "nativeName":"Kuanyama"
- },
- "la":{
- "name":"Latin",
- "nativeName":"latine, lingua latina"
- },
- "lb":{
- "name":"Luxembourgish",
- "nativeName":"Lëtzebuergesch"
- },
- "lg":{
- "name":"Luganda",
- "nativeName":"Luganda"
- },
- "li":{
- "name":"Limburgish",
- "nativeName":"Limburgs"
- },
- "ln":{
- "name":"Lingala",
- "nativeName":"Lingála"
- },
- "lo":{
- "name":"Lao",
- "nativeName":"ພາສາລາວ"
- },
- "lt":{
- "name":"Lithuanian",
- "nativeName":"lietuvių kalba"
- },
- "lu":{
- "name":"Luba-Katanga",
- "nativeName":""
- },
- "lv":{
- "name":"Latvian",
- "nativeName":"latviešu valoda"
- },
- "gv":{
- "name":"Manx",
- "nativeName":"Gaelg, Gailck"
- },
- "mk":{
- "name":"Macedonian",
- "nativeName":"македонски јазик"
- },
- "mg":{
- "name":"Malagasy",
- "nativeName":"Malagasy fiteny"
- },
- "ms":{
- "name":"Malay",
- "nativeName":"bahasa Melayu, بهاس ملايو"
- },
- "ml":{
- "name":"Malayalam",
- "nativeName":"മലയാളം"
- },
- "mt":{
- "name":"Maltese",
- "nativeName":"Malti"
- },
- "mi":{
- "name":"Māori",
- "nativeName":"te reo Māori"
- },
- "mr":{
- "name":"Marathi",
- "nativeName":"मराठी"
- },
- "mh":{
- "name":"Marshallese",
- "nativeName":"Kajin M̧ajeļ"
- },
- "mn":{
- "name":"Mongolian",
- "nativeName":"монгол"
- },
- "na":{
- "name":"Nauru",
- "nativeName":"Ekakairũ Naoero"
- },
- "nv":{
- "name":"Navajo",
- "nativeName":"Diné bizaad, Dinékʼehǰí"
- },
- "nb":{
- "name":"Norwegian Bokmål",
- "nativeName":"Norsk bokmål"
- },
- "nd":{
- "name":"North Ndebele",
- "nativeName":"isiNdebele"
- },
- "ne":{
- "name":"Nepali",
- "nativeName":"नेपाली"
- },
- "ng":{
- "name":"Ndonga",
- "nativeName":"Owambo"
- },
- "nn":{
- "name":"Norwegian Nynorsk",
- "nativeName":"Norsk nynorsk"
- },
- "no":{
- "name":"Norwegian",
- "nativeName":"Norsk"
- },
- "ii":{
- "name":"Nuosu",
- "nativeName":"ꆈꌠ꒿ Nuosuhxop"
- },
- "nr":{
- "name":"South Ndebele",
- "nativeName":"isiNdebele"
- },
- "oc":{
- "name":"Occitan",
- "nativeName":"Occitan"
- },
- "oj":{
- "name":"Ojibwe",
- "nativeName":"ᐊᓂᔑᓈᐯᒧᐎᓐ"
- },
- "cu":{
- "name":"Church Slavonic",
- "nativeName":"ѩзыкъ словѣньскъ"
- },
- "om":{
- "name":"Oromo",
- "nativeName":"Afaan Oromoo"
- },
- "or":{
- "name":"Oriya",
- "nativeName":"ଓଡ଼ିଆ"
- },
- "os":{
- "name":"Ossetian",
- "nativeName":"ирон æвзаг"
- },
- "pa":{
- "name":"Punjabi",
- "nativeName":"ਪੰਜਾਬੀ, پنجابی"
- },
- "pi":{
- "name":"Pāli",
- "nativeName":"पाऴि"
- },
- "fa":{
- "name":"Persian",
- "nativeName":"فارسی"
- },
- "pl":{
- "name":"Polish",
- "nativeName":"polski"
- },
- "ps":{
- "name":"Pashto",
- "nativeName":"پښتو"
- },
- "pt":{
- "name":"Portuguese",
- "nativeName":"Português"
- },
- "qu":{
- "name":"Quechua",
- "nativeName":"Runa Simi, Kichwa"
- },
- "rm":{
- "name":"Romansh",
- "nativeName":"rumantsch grischun"
- },
- "rn":{
- "name":"Kirundi",
- "nativeName":"kiRundi"
- },
- "ro":{
- "name":"Romanian",
- "nativeName":"română"
- },
- "ru":{
- "name":"Russian",
- "nativeName":"русский язык"
- },
- "sa":{
- "name":"Sanskrit",
- "nativeName":"संस्कृतम्"
- },
- "sc":{
- "name":"Sardinian",
- "nativeName":"sardu"
- },
- "sd":{
- "name":"Sindhi",
- "nativeName":"सिन्धी, سنڌي، سندھی"
- },
- "se":{
- "name":"Northern Sami",
- "nativeName":"Davvisámegiella"
- },
- "sm":{
- "name":"Samoan",
- "nativeName":"gagana faa Samoa"
- },
- "sg":{
- "name":"Sango",
- "nativeName":"yângâ tî sängö"
- },
- "sr":{
- "name":"Serbian",
- "nativeName":"српски језик"
- },
- "gd":{
- "name":"Gaelic",
- "nativeName":"Gàidhlig"
- },
- "sn":{
- "name":"Shona",
- "nativeName":"chiShona"
- },
- "si":{
- "name":"Sinhalese",
- "nativeName":"සිංහල"
- },
- "sk":{
- "name":"Slovak",
- "nativeName":"slovenčina"
- },
- "sl":{
- "name":"Slovene",
- "nativeName":"slovenščina"
- },
- "so":{
- "name":"Somali",
- "nativeName":"Soomaaliga, af Soomaali"
- },
- "st":{
- "name":"Southern Sotho",
- "nativeName":"Sesotho"
- },
- "es":{
- "name":"Spanish",
- "nativeName":"español, castellano"
- },
- "su":{
- "name":"Sundanese",
- "nativeName":"Basa Sunda"
- },
- "sw":{
- "name":"Swahili",
- "nativeName":"Kiswahili"
- },
- "ss":{
- "name":"Swati",
- "nativeName":"SiSwati"
- },
- "sv":{
- "name":"Swedish",
- "nativeName":"svenska"
- },
- "ta":{
- "name":"Tamil",
- "nativeName":"தமிழ்"
- },
- "te":{
- "name":"Telugu",
- "nativeName":"తెలుగు"
- },
- "tg":{
- "name":"Tajik",
- "nativeName":"тоҷикӣ, toğikī, تاجیکی"
- },
- "th":{
- "name":"Thai",
- "nativeName":"ไทย"
- },
- "ti":{
- "name":"Tigrinya",
- "nativeName":"ትግርኛ"
- },
- "bo":{
- "name":"Tibetan",
- "nativeName":"བོད་ཡིག"
- },
- "tk":{
- "name":"Turkmen",
- "nativeName":"Türkmen, Түркмен"
- },
- "tl":{
- "name":"Tagalog",
- "nativeName":"Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔"
- },
- "tn":{
- "name":"Tswana",
- "nativeName":"Setswana"
- },
- "to":{
- "name":"Tonga",
- "nativeName":"faka Tonga"
- },
- "tr":{
- "name":"Turkish",
- "nativeName":"Türkçe"
- },
- "ts":{
- "name":"Tsonga",
- "nativeName":"Xitsonga"
- },
- "tt":{
- "name":"Tatar",
- "nativeName":"татарча, tatarça, تاتارچا"
- },
- "tw":{
- "name":"Twi",
- "nativeName":"Twi"
- },
- "ty":{
- "name":"Tahitian",
- "nativeName":"Reo Tahiti"
- },
- "ug":{
- "name":"Uyghur",
- "nativeName":"Uyƣurqə, ئۇيغۇرچە"
- },
- "uk":{
- "name":"Ukrainian",
- "nativeName":"українська"
- },
- "ur":{
- "name":"Urdu",
- "nativeName":"اردو"
- },
- "uz":{
- "name":"Uzbek",
- "nativeName":"zbek, Ўзбек, أۇزبېك"
- },
- "ve":{
- "name":"Venda",
- "nativeName":"Tshivenḓa"
- },
- "vi":{
- "name":"Vietnamese",
- "nativeName":"Tiếng Việt"
- },
- "vo":{
- "name":"Volapük",
- "nativeName":"Volapük"
- },
- "wa":{
- "name":"Walloon",
- "nativeName":"Walon"
- },
- "cy":{
- "name":"Welsh",
- "nativeName":"Cymraeg"
- },
- "wo":{
- "name":"Wolof",
- "nativeName":"Wollof"
- },
- "fy":{
- "name":"Western Frisian",
- "nativeName":"Frysk"
- },
- "xh":{
- "name":"Xhosa",
- "nativeName":"isiXhosa"
- },
- "yi":{
- "name":"Yiddish",
- "nativeName":"ייִדיש"
- },
- "yo":{
- "name":"Yoruba",
- "nativeName":"Yorùbá"
- },
- "za":{
- "name":"Zhuang",
- "nativeName":"Saɯ cueŋƅ, Saw cuengh"
- },
- "ar-dz":{
- "name":"Arabic (Algeria)",
- "nativeName":"العربية (الجزائر)"
- },
- "ar-bh":{
- "name":"Arabic (Bahrain)",
- "nativeName":"العربية (البحرين)"
- },
- "ar-eg":{
- "name":"Arabic (Egypt)",
- "nativeName":"العربية (مصر)"
- },
- "ar-iq":{
- "name":"Arabic (Iraq)",
- "nativeName":"العربية (العراق)"
- },
- "ar-jo":{
- "name":"Arabic (Jordan)",
- "nativeName":"العربية (الأردن)"
- },
- "ar-kw":{
- "name":"Arabic (Kuwait)",
- "nativeName":"العربية (الكويت)"
- },
- "ar-lb":{
- "name":"Arabic (Lebanon)",
- "nativeName":"العربية (لبنان)"
- },
- "ar-ly":{
- "name":"Arabic (Libya)",
- "nativeName":"العربية (ليبيا)"
- },
- "ar-ma":{
- "name":"Arabic (Morocco)",
- "nativeName":"العربية (المملكة المغربية)"
- },
- "ar-om":{
- "name":"Arabic (Oman)",
- "nativeName":"العربية (عمان)"
- },
- "ar-qa":{
- "name":"Arabic (Qatar)",
- "nativeName":"العربية (قطر)"
- },
- "ar-sa":{
- "name":"Arabic (Saudi Arabia)",
- "nativeName":"العربية (المملكة العربية السعودية)"
- },
- "ar-sy":{
- "name":"Arabic (Syria)",
- "nativeName":"العربية (سوريا)"
- },
- "ar-tn":{
- "name":"Arabic (Tunisia)",
- "nativeName":"العربية (تونس)"
- },
- "ar-ae":{
- "name":"Arabic (U.A.E.)",
- "nativeName":"العربية (الإمارات العربية المتحدة)"
- },
- "ar-ye":{
- "name":"Arabic (Yemen)",
- "nativeName":"العربية (اليمن)"
- },
- "de-at":{
- "name":"German (Austria)",
- "nativeName":"Deutsch (Österreich)"
- },
- "de-li":{
- "name":"German (Liechtenstein)",
- "nativeName":"Deutsch (Liechtenstein)"
- },
- "de-lu":{
- "name":"German (Luxembourg)",
- "nativeName":"Deutsch (Luxemburg)"
- },
- "de-ch":{
- "name":"German (Switzerland)",
- "nativeName":"Deutsch (Schweiz)"
- },
- "en-au":{
- "name":"English (Australia)",
- "nativeName":"English (Australia)"
- },
- "en-bz":{
- "name":"English (Belize)",
- "nativeName":"English (Belize)"
- },
- "en-ca":{
- "name":"English (Canada)",
- "nativeName":"English (Canada)"
- },
- "en-ie":{
- "name":"English (Ireland)",
- "nativeName":"English (Ireland)"
- },
- "en-jm":{
- "name":"English (Jamaica)",
- "nativeName":"English (Jamaica)"
- },
- "en-nz":{
- "name":"English (New Zealand)",
- "nativeName":""
- },
- "en-za":{
- "name":"English (South Africa)",
- "nativeName":"English (South Africa)"
- },
- "en-tt":{
- "name":"English (Trinidad)",
- "nativeName":"English (Trinidad y Tobago)"
- },
- "en-gb":{
- "name":"English (United Kingdom)",
- "nativeName":"English (United Kingdom)"
- },
- "en-us":{
- "name":"English (United States)",
- "nativeName":"English (United States)"
- },
- "es-ar":{
- "name":"Spanish (Argentina)",
- "nativeName":"Español (Argentina)"
- },
- "es-bo":{
- "name":"Spanish (Bolivia)",
- "nativeName":"Español (Bolivia)"
- },
- "es-cl":{
- "name":"Spanish (Chile)",
- "nativeName":"Español (Chile)"
- },
- "es-co":{
- "name":"Spanish (Colombia)",
- "nativeName":"Español (Colombia)"
- },
- "es-cr":{
- "name":"Spanish (Costa Rica)",
- "nativeName":"Español (Costa Rica)"
- },
- "es-do":{
- "name":"Spanish (Dominican Republic)",
- "nativeName":"Español (República Dominicana)"
- },
- "es-ec":{
- "name":"Spanish (Ecuador)",
- "nativeName":"Español (Ecuador)"
- },
- "es-sv":{
- "name":"Spanish (El Salvador)",
- "nativeName":"Español (El Salvador)"
- },
- "es-gt":{
- "name":"Spanish (Guatemala)",
- "nativeName":"Español (Guatemala)"
- },
- "es-hn":{
- "name":"Spanish (Honduras)",
- "nativeName":"Español (Honduras)"
- },
- "es-mx":{
- "name":"Spanish (Mexico)",
- "nativeName":"Español (México)"
- },
- "es-ni":{
- "name":"Spanish (Nicaragua)",
- "nativeName":"Español (Nicaragua)"
- },
- "es-pa":{
- "name":"Spanish (Panama)",
- "nativeName":"Español (Panamá)"
- },
- "es-py":{
- "name":"Spanish (Paraguay)",
- "nativeName":"Español (Paraguay)"
- },
- "es-pe":{
- "name":"Spanish (Peru)",
- "nativeName":"Español (Perú)"
- },
- "es-pr":{
- "name":"Spanish (Puerto Rico)",
- "nativeName":"Español (Puerto Rico)"
- },
- "es-uy":{
- "name":"Spanish (Uruguay)",
- "nativeName":"Español (Uruguay)"
- },
- "es-ve":{
- "name":"Spanish (Venezuela)",
- "nativeName":"Español (Venezuela)"
- },
- "fr-be":{
- "name":"French (Belgium)",
- "nativeName":"français (Belgique)"
- },
- "fr-ca":{
- "name":"French (Canada)",
- "nativeName":"français (Canada)"
- },
- "fr-lu":{
- "name":"French (Luxembourg)",
- "nativeName":"français (Luxembourg)"
- },
- "fr-ch":{
- "name":"French (Switzerland)",
- "nativeName":"français (Suisse)"
- },
- "it-ch":{
- "name":"Italian (Switzerland)",
- "nativeName":"italiano (Svizzera)"
- },
- "nl-be":{
- "name":"Dutch (Belgium)",
- "nativeName":"Nederlands (België)"
- },
- "pt-br":{
- "name":"Portuguese (Brazil)",
- "nativeName":"Português (Brasil)"
- },
- "sv-fi":{
- "name":"Swedish (Finland)",
- "nativeName":"svenska (Finland)"
- },
- "zh-hk":{
- "name":"Chinese (Hong Kong)",
- "nativeName":"中文(香港特别行政區)"
- },
- "zh-cn":{
- "name":"Chinese (PRC)",
- "nativeName":"中文(中华人民共和国)"
- },
- "zh-sg":{
- "name":"Chinese (Singapore)",
- "nativeName":"中文(新加坡)"
- },
- "zh-tw":{
- "name":"Chinese Traditional (Taiwan)",
- "nativeName":"中文(台灣)"
- }
- }
-
- AblePlayer.prototype.getLanguageName = function (key) {
- // key = key.slice(0,2);
- var lang = isoLangs[key];
- return lang ? lang.name : undefined;
- };
- AblePlayer.prototype.getLanguageNativeName = function (key) {
- // key = key.slice(0,2);
- var lang = isoLangs[key];
- return lang ? lang.nativeName : undefined;
- }
-
-})(jQuery);
-(function ($) {
- AblePlayer.prototype.getSupportedLangs = function() {
- // returns an array of languages for which AblePlayer has translation tables
- var langs = ['ca','de','en','es','fr','he','it','ja','nb','nl','pt-br','tr','zh-tw'];
- return langs;
- };
-
- AblePlayer.prototype.getTranslationText = function() {
- // determine language, then get labels and prompts from corresponding translation var
- var deferred, thisObj, lang, thisObj, msg, translationFile, collapsedLang;
- deferred = $.Deferred();
-
- thisObj = this;
- // get language of the web page, if specified
- if ($('body').attr('lang')) {
- lang = $('body').attr('lang').toLowerCase();
- }
- else if ($('html').attr('lang')) {
- lang = $('html').attr('lang').toLowerCase();
- }
- else {
- lang = null;
- }
-
- // override this.lang to language of the web page, if known and supported
- // otherwise this.lang will continue using default
- if (!this.forceLang) {
- if (lang) {
- if (lang !== this.lang) {
- if ($.inArray(lang,this.getSupportedLangs()) !== -1) {
- // this is a supported lang
- this.lang = lang;
- }
- else {
- msg = lang + ' is not currently supported. Using default language (' + this.lang + ')';
- if (this.debug) {
-
- }
- }
- }
- }
- }
- if (!this.searchLang) {
- this.searchLang = this.lang;
- }
- translationFile = this.rootPath + 'translations/' + this.lang + '.js';
- this.importTranslationFile(translationFile).then(function(result) {
- collapsedLang = thisObj.lang.replace('-','');
- thisObj.tt = eval(collapsedLang);
- deferred.resolve();
- });
- return deferred.promise();
- };
-
- AblePlayer.prototype.importTranslationFile = function(translationFile) {
-
- var deferred = $.Deferred();
- $.getScript(translationFile)
- .done(function(translationVar,textStatus) {
- // translation file successfully retrieved
- deferred.resolve(translationVar);
- })
- .fail(function(jqxhr, settings, exception) {
- deferred.fail();
- // error retrieving file
- // TODO: handle this
- });
- return deferred.promise();
- };
-
-})(jQuery);
-
-(function($) {
- AblePlayer.prototype.computeEndTime = function(startTime, durationTime) {
- var SECONDS = 0;
- var MINUTES = 1;
- var HOURS = 2;
-
- var startParts = startTime
- .split(':')
- .reverse()
- .map(function(value) {
- return parseFloat(value);
- });
-
- var durationParts = durationTime
- .split(':')
- .reverse()
- .map(function(value) {
- return parseFloat(value);
- });
-
- var endTime = startParts
- .reduce(function(acc, val, index) {
- var sum = val + durationParts[index];
-
- if (index === SECONDS) {
- if (sum > 60) {
- durationParts[index + 1] += 1;
- sum -= 60;
- }
-
- sum = sum.toFixed(3);
- }
-
- if (index === MINUTES) {
- if (sum > 60) {
- durationParts[index + 1] += 1;
- sum -= 60;
- }
- }
-
- if (sum < 10) {
- sum = '0' + sum;
- }
-
- acc.push(sum);
-
- return acc;
- }, [])
- .reverse()
- .join(':');
-
- return endTime;
- };
-
- AblePlayer.prototype.ttml2webvtt = function(contents) {
- var thisObj = this;
-
- var xml = thisObj.convert.xml2json(contents, {
- ignoreComment: true,
- alwaysChildren: true,
- compact: true,
- spaces: 2
- });
-
- var vttHeader = 'WEBVTT\n\n\n';
- var captions = JSON.parse(xml).tt.body.div.p;
-
- var vttCaptions = captions.reduce(function(acc, value, index) {
- var text = value._text;
- var isArray = Array.isArray(text);
- var attributes = value._attributes;
- var endTime = thisObj.computeEndTime(attributes.begin, attributes.dur);
-
- var caption =
- thisObj.computeEndTime(attributes.begin, '00:00:0') +
- ' --> ' +
- thisObj.computeEndTime(attributes.begin, attributes.dur) +
- '\n' +
- (isArray ? text.join('\n') : text) +
- '\n\n';
-
- return acc + caption;
- }, vttHeader);
-
- return vttCaptions;
- };
-})(jQuery);
-
-/*! Copyright (c) 2014 - Paul Tavares - purtuga - @paul_tavares - MIT License */
-;(function($){
-
- /**
- * Delays the execution of a function until an expression returns true.
- * The expression is checked every 100 milliseconds for as many tries
- * as defined in in the attempts option
- *
- * @param {Object} options
- * @param {Function} options.when
- * Function to execute on every interval.
- * Must return true (boolean) in order for
- * options.do to be executed.
- * @param {Function} [options.exec]
- * Function to be executed once options.when()
- * returns true.
- * @param {Interger} [options.interval=100]
- * How long to wait in-between tries.
- * @param {Interger} [options.attempts=100]
- * How many tries to use before its considered
- * a failure.
- * @param {Interger} [options.delayed=0]
- * Number of miliseconds to wait before execution
- is started. Default is imediately.
- *
- * @return {jQuery.Promise}
- *
- * @example
- *
- * $.doWhen({
- * when: function(){
- * return false;
- * },
- * exec: function(){
- * alert("never called given false response on when param!");
- * }
- * })
- * .fail(function(){
- * alert('ALERT: FAILED CONDITION');
- * })
- * .then(function(){
- * alert("resolved.");
- * });
- *
- */
- $.doWhen = function(options) {
-
- return $.Deferred(function(dfd){
-
- var opt = $.extend({}, {
- when: null,
- exec: function(){},
- interval: 100,
- attempts: 100,
- delayed: 0
- },
- options,
- {
- checkId: null
- }),
- startChecking = function(){
-
- // Check condition now and if true, then resolve object
- if (opt.when() === true) {
-
- opt.exec.call(dfd.promise());
- dfd.resolve();
- return;
-
- }
-
- // apply minimal UI and hide the overlay
- opt.checkId = setInterval(function(){
-
- if (opt.attempts === 0) {
-
- clearInterval(opt.checkId);
- dfd.reject();
-
- } else {
-
- --opt.attempts;
-
- if (opt.when() === true) {
-
- opt.attempts = 0;
- clearInterval(opt.checkId);
- opt.exec.call(dfd.promise());
- dfd.resolve();
-
- }
-
- }
-
- }, opt.interval);
-
- };
-
- if (opt.delayed > 0) {
-
- setTimeout(startChecking, Number(opt.delayed));
-
- } else {
-
- startChecking();
-
- }
-
- }).promise();
-
- };
-
-})(jQuery);
-/* Video Transcript Sorter (VTS)
- * Used to synchronize time stamps from WebVTT resources
- * so they appear in the proper sequence within an auto-generated interactive transcript
-*/
-
-(function ($) {
- AblePlayer.prototype.injectVTS = function() {
-
- // To add a transcript sorter to a web page:
- // Add
to the web page
-
- // Define all variables
- var thisObj, tracks, $heading;
- var $instructions, $p1, $p2, $ul, $li1, $li2, $li3;
- var $fieldset, $legend, i, $radioDiv, radioId, $label, $radio;
- var $saveButton, $savedTable;
-
- thisObj = this;
-
- if ($('#able-vts').length) {
- // Page includes a container for a VTS instance
-
- // Are they qualifying tracks?
- if (this.vtsTracks.length) {
- // Yes - there are!
-
- // Build an array of unique languages
- this.langs = [];
- this.getAllLangs(this.vtsTracks);
-
- // Set the default VTS language
- this.vtsLang = this.lang;
-
- // Inject a heading
- $heading = $('
').text('Video Transcript Sorter'); // TODO: Localize; intelligently assign proper heading level
- $('#able-vts').append($heading);
-
- // Inject an empty div for writing messages
- this.$vtsAlert = $('',{
- 'id': 'able-vts-alert',
- 'aria-live': 'polite',
- 'aria-atomic': 'true'
- })
- $('#able-vts').append(this.$vtsAlert);
-
- // Inject instructions (TODO: Localize)
- $instructions = $('
',{
- 'id': 'able-vts-instructions'
- });
- $p1 = $('
').text('Use the Video Transcript Sorter to perform any of the following tasks:');
- $ul = $('
');
- $li1 = $('').text('Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player\'s auto-generated transcript.');
- $li2 = $(' ').text('Modify content or start/end times (all are directly editable within the table).');
- $li3 = $(' ').text('Insert new content, such as chapters or descriptions.');
- $p2 = $('').text('When finished editing, click the "Save Changes" button. This will auto-generate new content for all relevant timed text files (chapters, descriptions, captions, and/or subtitles), which can be copied and pasted into separate WebVTT files for use by Able Player.');
- $ul.append($li1,$li2,$li3);
- $instructions.append($p1,$ul,$p2);
- $('#able-vts').append($instructions);
-
- // Inject a fieldset with radio buttons for each language
- $fieldset = $('
');
- $legend = $('').text('Select a language'); // TODO: Localize this
- $fieldset.append($legend)
- for (i in this.langs) {
- radioId = 'vts-lang-radio-' + this.langs[i];
- $radioDiv = $('',{
- // uncomment the following if label is native name
- // 'lang': this.langs[i]
- });
- $radio = $('
', {
- 'type': 'radio',
- 'name': 'vts-lang',
- 'id': radioId,
- 'value': this.langs[i]
- }).on('click',function() {
- thisObj.vtsLang = $(this).val();
- thisObj.showVtsAlert('Loading ' + thisObj.getLanguageName(thisObj.vtsLang) + ' tracks');
- thisObj.injectVtsTable('update',thisObj.vtsLang);
- });
- if (this.langs[i] == this.lang) {
- // this is the default language.
- $radio.prop('checked',true);
- }
- $label = $('
', {
- 'for': radioId
- // Two options for label:
- // getLanguageNativeName() - returns native name; if using this be sure to add lang attr to (see above)
- // getLanguageName() - returns name in English; doesn't require lang attr on
- }).text(this.getLanguageName(this.langs[i]));
- $radioDiv.append($radio,$label);
- $fieldset.append($radioDiv);
- }
- $('#able-vts').append($fieldset);
-
- // Inject a 'Save Changes' button
- $saveButton = $('',{
- 'type': 'button',
- 'id': 'able-vts-save',
- 'value': 'save'
- }).text('Save Changes'); // TODO: Localize this
- $('#able-vts').append($saveButton);
-
- // Inject a table with one row for each cue in the default language
- this.injectVtsTable('add',this.vtsLang);
-
- // TODO: Add drag/drop functionality for mousers
-
- // Add event listeners for contenteditable cells
- var kindOptions, beforeEditing, editedCell, editedContent, i, closestKind;
- kindOptions = ['captions','chapters','descriptions','subtitles'];
- $('td[contenteditable="true"]').on('focus',function() {
- beforeEditing = $(this).text();
- }).on('blur',function() {
- if (beforeEditing != $(this).text()) {
- editedCell = $(this).index();
- editedContent = $(this).text();
- if (editedCell === 1) {
- // do some simple spelling auto-correct
- if ($.inArray(editedContent,kindOptions) === -1) {
- // whatever user typed is not a valid kind
- // assume they correctly typed the first character
- if (editedContent.substr(0,1) === 's') {
- $(this).text('subtitles');
- }
- else if (editedContent.substr(0,1) === 'd') {
- $(this).text('descriptions');
- }
- else if (editedContent.substr(0,2) === 'ch') {
- $(this).text('chapters');
- }
- else {
- // whatever else they types, assume 'captions'
- $(this).text('captions');
- }
- }
- }
- else if (editedCell === 2 || editedCell === 3) {
- // start or end time
- // ensure proper formatting (with 3 decimal places)
- $(this).text(thisObj.formatTimestamp(editedContent));
- }
- }
- }).on('keydown',function(e) {
- // don't allow keystrokes to trigger Able Player (or other) functions
- // while user is editing
- e.stopPropagation();
- });
-
- // handle click on the Save button
-
- // handle click on the Save button
- $('#able-vts-save').on('click',function(e) {
- e.stopPropagation();
- if ($(this).attr('value') == 'save') {
- // replace table with WebVTT output in textarea fields (for copying/pasting)
- $(this).attr('value','cancel').text('Return to Editor'); // TODO: Localize this
- $savedTable = $('#able-vts table');
- $('#able-vts-instructions').hide();
- $('#able-vts > fieldset').hide();
- $('#able-vts table').remove();
- $('#able-vts-icon-credit').remove();
- thisObj.parseVtsOutput($savedTable);
- }
- else {
- // cancel saving, and restore the table using edited content
- $(this).attr('value','save').text('Save Changes'); // TODO: Localize this
- $('#able-vts-output').remove();
- $('#able-vts-instructions').show();
- $('#able-vts > fieldset').show();
- $('#able-vts').append($savedTable);
- $('#able-vts').append(thisObj.getIconCredit());
- thisObj.showVtsAlert('Cancelling saving. Any edits you made have been restored in the VTS table.'); // TODO: Localize this
- }
- });
- }
- }
- };
-
- AblePlayer.prototype.setupVtsTracks = function(kind, lang, label, src, contents) {
-
- // Called from tracks.js
-
- var srcFile, vtsCues;
-
- srcFile = this.getFilenameFromPath(src);
- vtsCues = this.parseVtsTracks(contents);
-
- this.vtsTracks.push({
- 'kind': kind,
- 'language': lang,
- 'label': label,
- 'srcFile': srcFile,
- 'cues': vtsCues
- });
- };
-
- AblePlayer.prototype.getFilenameFromPath = function(path) {
-
- var lastSlash;
-
- lastSlash = path.lastIndexOf('/');
- if (lastSlash === -1) {
- // there are no slashes in path.
- return path;
- }
- else {
- return path.substr(lastSlash+1);
- }
- };
-
- AblePlayer.prototype.getFilenameFromTracks = function(kind,lang) {
-
- for (var i=0; i ') !== -1) {
- // this is probably a time row
- timeParts = thisRow.trim().split(' ');
- if (this.isValidTimestamp(timeParts[0]) && this.isValidTimestamp(timeParts[2])) {
- // both timestamps are valid. This is definitely a time row
- content = '';
- j = i+1;
- blankRow = false;
- while (j < rows.length && !blankRow) {
- nextRow = rows[j].trim();
- if (nextRow.length > 0) {
- if (content.length > 0) {
- // add back the EOL between rows of content
- content += "\n" + nextRow;
- }
- else {
- // this is the first row of content. No need for an EOL
- content += nextRow;
- }
- }
- else {
- blankRow = true;
- }
- j++;
- }
- cues.push({
- 'start': timeParts[0],
- 'end': timeParts[2],
- 'content': content
- });
- i = j; //skip ahead
- }
- }
- else {
- i++;
- }
- }
- return cues;
- };
-
- AblePlayer.prototype.isValidTimestamp = function(timestamp) {
-
- // return true if timestamp contains only numbers or expected punctuation
- if (/^[0-9:,.]*$/.test(timestamp)) {
- return true;
- }
- else {
- return false;
- }
- };
-
- AblePlayer.prototype.formatTimestamp = function(timestamp) {
-
- // timestamp is a string in the form "HH:MM:SS.xxx"
- // Take some simple steps to ensure edited timestamp values still adhere to expected format
-
- var firstPart, lastPart;
-
- var firstPart = timestamp.substr(0,timestamp.lastIndexOf('.')+1);
- var lastPart = timestamp.substr(timestamp.lastIndexOf('.')+1);
-
- // TODO: Be sure each component within firstPart has only exactly two digits
- // Probably can't justify doing this automatically
- // If users enters '5' for minutes, that could be either '05' or '50'
- // This should trigger an error and prompt the user to correct the value before proceeding
-
- // Be sure lastPart has exactly three digits
- if (lastPart.length > 3) {
- // chop off any extra digits
- lastPart = lastPart.substr(0,3);
- }
- else if (lastPart.length < 3) {
- // add trailing zeros
- while (lastPart.length < 3) {
- lastPart += '0';
- }
- }
- return firstPart + lastPart;
- };
-
-
- AblePlayer.prototype.injectVtsTable = function(action,lang) {
-
- // action is either 'add' (for a new table) or 'update' (if user has selected a new lang)
-
- var $table, headers, i, $tr, $th, $td, rows, rowNum, rowId;
-
- if (action === 'update') {
- // remove existing table
- $('#able-vts table').remove();
- $('#able-vts-icon-credit').remove();
- }
-
- $table = $('',{
- 'lang': lang
- });
- $tr = $('',{
- 'lang': 'en' // TEMP, until header row is localized
- });
- headers = ['Row #','Kind','Start','End','Content','Actions']; // TODO: Localize this
- for (i=0; i < headers.length; i++) {
- $th = $('', {
- 'scope': 'col'
- }).text(headers[i]);
- if (headers[i] === 'Actions') {
- $th.addClass('actions');
- }
- $tr.append($th);
- }
- $table.append($tr);
-
- // Get all rows (sorted by start time), and inject them into table
- rows = this.getAllRows(lang);
- for (i=0; i < rows.length; i++) {
- rowNum = i + 1;
- rowId = 'able-vts-row-' + rowNum;
- $tr = $(' ',{
- 'id': rowId,
- 'class': 'kind-' + rows[i].kind
- });
- // Row #
- $td = $('').text(rowNum);
- $tr.append($td);
-
- // Kind
- $td = $(' ',{
- 'contenteditable': 'true'
- }).text(rows[i].kind);
- $tr.append($td);
-
- // Start
- $td = $(' ',{
- 'contenteditable': 'true'
- }).text(rows[i].start);
- $tr.append($td);
-
- // End
- $td = $(' ',{
- 'contenteditable': 'true'
- }).text(rows[i].end);
- $tr.append($td);
-
- // Content
- $td = $(' ',{
- 'contenteditable': 'true'
- }).text(rows[i].content); // TODO: Preserve tags
- $tr.append($td);
-
- // Actions
- $td = this.addVtsActionButtons(rowNum,rows.length);
- $tr.append($td);
-
- $table.append($tr);
- }
- $('#able-vts').append($table);
-
- // Add credit for action button SVG icons
- $('#able-vts').append(this.getIconCredit());
-
- };
-
- AblePlayer.prototype.addVtsActionButtons = function(rowNum,numRows) {
-
- // rowNum is the number of the current table row (starting with 1)
- // numRows is the total number of rows (excluding the header row)
- // TODO: Position buttons so they're vertically aligned, even if missing an Up or Down button
- var thisObj, $td, buttons, i, button, $button, $svg, $g, pathString, pathString2, $path, $path2;
- thisObj = this;
- $td = $(' ');
- buttons = ['up','down','insert','delete'];
-
- for (i=0; i < buttons.length; i++) {
- button = buttons[i];
- if (button === 'up') {
- if (rowNum > 1) {
- $button = $('',{
- 'id': 'able-vts-button-up-' + rowNum,
- 'title': 'Move up',
- 'aria-label': 'Move Row ' + rowNum + ' up'
- }).on('click', function(el) {
- thisObj.onClickVtsActionButton(el.currentTarget);
- });
- $svg = $('',{
- 'focusable': 'false',
- 'aria-hidden': 'true',
- 'x': '0px',
- 'y': '0px',
- 'width': '254.296px',
- 'height': '254.296px',
- 'viewBox': '0 0 254.296 254.296',
- 'style': 'enable-background:new 0 0 254.296 254.296'
- });
- pathString = 'M249.628,176.101L138.421,52.88c-6.198-6.929-16.241-6.929-22.407,0l-0.381,0.636L4.648,176.101'
- + 'c-6.198,6.897-6.198,18.052,0,24.981l0.191,0.159c2.892,3.305,6.865,5.371,11.346,5.371h221.937c4.577,0,8.613-2.161,11.41-5.594'
- + 'l0.064,0.064C255.857,194.153,255.857,182.998,249.628,176.101z';
- $path = $('',{
- 'd': pathString
- });
- $g = $('').append($path);
- $svg.append($g);
- $button.append($svg);
- // Refresh button in the DOM in order for browser to process & display the SVG
- $button.html($button.html());
- $td.append($button);
- }
- }
- else if (button === 'down') {
- if (rowNum < numRows) {
- $button = $('',{
- 'id': 'able-vts-button-down-' + rowNum,
- 'title': 'Move down',
- 'aria-label': 'Move Row ' + rowNum + ' down'
- }).on('click', function(el) {
- thisObj.onClickVtsActionButton(el.currentTarget);
- });
- $svg = $('',{
- 'focusable': 'false',
- 'aria-hidden': 'true',
- 'x': '0px',
- 'y': '0px',
- 'width': '292.362px',
- 'height': '292.362px',
- 'viewBox': '0 0 292.362 292.362',
- 'style': 'enable-background:new 0 0 292.362 292.362'
- });
- pathString = 'M286.935,69.377c-3.614-3.617-7.898-5.424-12.848-5.424H18.274c-4.952,0-9.233,1.807-12.85,5.424'
- + 'C1.807,72.998,0,77.279,0,82.228c0,4.948,1.807,9.229,5.424,12.847l127.907,127.907c3.621,3.617,7.902,5.428,12.85,5.428'
- + 's9.233-1.811,12.847-5.428L286.935,95.074c3.613-3.617,5.427-7.898,5.427-12.847C292.362,77.279,290.548,72.998,286.935,69.377z';
- $path = $('',{
- 'd': pathString
- });
- $g = $('').append($path);
- $svg.append($g);
- $button.append($svg);
- // Refresh button in the DOM in order for browser to process & display the SVG
- $button.html($button.html());
- $td.append($button);
- }
- }
- else if (button === 'insert') {
- // Add Insert button to all rows
- $button = $('',{
- 'id': 'able-vts-button-insert-' + rowNum,
- 'title': 'Insert row below',
- 'aria-label': 'Insert row before Row ' + rowNum
- }).on('click', function(el) {
- thisObj.onClickVtsActionButton(el.currentTarget);
- });
- $svg = $('',{
- 'focusable': 'false',
- 'aria-hidden': 'true',
- 'x': '0px',
- 'y': '0px',
- 'width': '401.994px',
- 'height': '401.994px',
- 'viewBox': '0 0 401.994 401.994',
- 'style': 'enable-background:new 0 0 401.994 401.994'
- });
- pathString = 'M394,154.175c-5.331-5.33-11.806-7.994-19.417-7.994H255.811V27.406c0-7.611-2.666-14.084-7.994-19.414'
- + 'C242.488,2.666,236.02,0,228.398,0h-54.812c-7.612,0-14.084,2.663-19.414,7.993c-5.33,5.33-7.994,11.803-7.994,19.414v118.775'
- + 'H27.407c-7.611,0-14.084,2.664-19.414,7.994S0,165.973,0,173.589v54.819c0,7.618,2.662,14.086,7.992,19.411'
- + 'c5.33,5.332,11.803,7.994,19.414,7.994h118.771V374.59c0,7.611,2.664,14.089,7.994,19.417c5.33,5.325,11.802,7.987,19.414,7.987'
- + 'h54.816c7.617,0,14.086-2.662,19.417-7.987c5.332-5.331,7.994-11.806,7.994-19.417V255.813h118.77'
- + 'c7.618,0,14.089-2.662,19.417-7.994c5.329-5.325,7.994-11.793,7.994-19.411v-54.819C401.991,165.973,399.332,159.502,394,154.175z';
- $path = $('',{
- 'd': pathString
- });
- $g = $('').append($path);
- $svg.append($g);
- $button.append($svg);
- // Refresh button in the DOM in order for browser to process & display the SVG
- $button.html($button.html());
- $td.append($button);
- }
- else if (button === 'delete') {
- // Add Delete button to all rows
- $button = $('',{
- 'id': 'able-vts-button-delete-' + rowNum,
- 'title': 'Delete row ',
- 'aria-label': 'Delete Row ' + rowNum
- }).on('click', function(el) {
- thisObj.onClickVtsActionButton(el.currentTarget);
- });
- $svg = $('',{
- 'focusable': 'false',
- 'aria-hidden': 'true',
- 'x': '0px',
- 'y': '0px',
- 'width': '508.52px',
- 'height': '508.52px',
- 'viewBox': '0 0 508.52 508.52',
- 'style': 'enable-background:new 0 0 508.52 508.52'
- });
- pathString = 'M397.281,31.782h-63.565C333.716,14.239,319.478,0,301.934,0h-95.347'
- + 'c-17.544,0-31.782,14.239-31.782,31.782h-63.565c-17.544,0-31.782,14.239-31.782,31.782h349.607'
- + 'C429.063,46.021,414.825,31.782,397.281,31.782z';
- $path = $('',{
- 'd': pathString
- });
- pathString2 = 'M79.456,476.737c0,17.544,14.239,31.782,31.782,31.782h286.042'
- + 'c17.544,0,31.782-14.239,31.782-31.782V95.347H79.456V476.737z M333.716,174.804c0-8.772,7.151-15.891,15.891-15.891'
- + 'c8.74,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.151,15.891-15.891,15.891c-8.74,0-15.891-7.151-15.891-15.891V174.804z'
- + 'M238.369,174.804c0-8.772,7.119-15.891,15.891-15.891c8.74,0,15.891,7.119,15.891,15.891v254.26'
- + 'c0,8.74-7.151,15.891-15.891,15.891c-8.772,0-15.891-7.151-15.891-15.891V174.804z M143.021,174.804'
- + 'c0-8.772,7.119-15.891,15.891-15.891c8.772,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.119,15.891-15.891,15.891'
- + 'c-8.772,0-15.891-7.151-15.891-15.891V174.804z';
- $path2 = $('',{
- 'd': pathString2
- });
-
- $g = $('').append($path,$path2);
- $svg.append($g);
- $button.append($svg);
- // Refresh button in the DOM in order for browser to process & display the SVG
- $button.html($button.html());
- $td.append($button);
- }
- }
- return $td;
- };
-
- AblePlayer.prototype.updateVtsActionButtons = function($buttons,nextRowNum) {
-
- // TODO: Add some filters to this function to add or delete 'Up' and 'Down' buttons
- // if row is moved to/from the first/last rows
- var i, $thisButton, id, label, newId, newLabel;
- for (i=0; i < $buttons.length; i++) {
- $thisButton = $buttons.eq(i);
- id = $thisButton.attr('id');
- label = $thisButton.attr('aria-label');
- // replace the integer (id) within each of the above strings
- newId = id.replace(/[0-9]+/g, nextRowNum);
- newLabel = label.replace(/[0-9]+/g, nextRowNum);
- $thisButton.attr('id',newId);
- $thisButton.attr('aria-label',newLabel);
- }
- }
-
- AblePlayer.prototype.getIconCredit = function() {
-
- var credit;
- credit = '';
- return credit;
- };
-
- AblePlayer.prototype.getAllLangs = function(tracks) {
-
- // update this.langs with any unique languages found in tracks
- var i;
- for (i in tracks) {
- if (tracks[i].hasOwnProperty('language')) {
- if ($.inArray(tracks[i].language,this.langs) === -1) {
- // this language is not already in the langs array. Add it.
- this.langs[this.langs.length] = tracks[i].language;
- }
- }
- }
- };
-
- AblePlayer.prototype.getAllRows = function(lang) {
-
- // returns an array of data to be displayed in VTS table
- // includes all cues for tracks of any type with matching lang
- // cues are sorted by start time
- var i, track, c, cues;
- cues = [];
- for (i=0; i < this.vtsTracks.length; i++) {
- track = this.vtsTracks[i];
- if (track.language == lang) {
- // this track matches the language. Add its cues to array
- for (c in track.cues) {
- cues.push({
- 'kind': track.kind,
- 'lang': lang,
- 'id': track.cues[c].id,
- 'start': track.cues[c].start,
- 'end': track.cues[c].end,
- 'content': track.cues[c].content
- });
- }
- }
- }
- // Now sort cues by start time
- cues.sort(function(a,b) {
- return a.start > b.start ? 1 : -1;
- });
- return cues;
- };
-
-
- AblePlayer.prototype.onClickVtsActionButton = function(el) {
-
- // handle click on up, down, insert, or delete button
- var idParts, action, rowNum;
- idParts = $(el).attr('id').split('-');
- action = idParts[3];
- rowNum = idParts[4];
- if (action == 'up') {
- // move the row up
- this.moveRow(rowNum,'up');
- }
- else if (action == 'down') {
- // move the row down
- this.moveRow(rowNum,'down');
- }
- else if (action == 'insert') {
- // insert a row below
- this.insertRow(rowNum);
- }
- else if (action == 'delete') {
- // delete the row
- this.deleteRow(rowNum);
- }
- };
-
- AblePlayer.prototype.insertRow = function(rowNum) {
-
- // Insert empty row below rowNum
- var $table, $rows, numRows, newRowNum, newRowId, newTimes, $tr, $td;
- var $select, options, i, $option, newKind, newClass, $parentRow;
- var i, nextRowNum, $buttons;
-
- $table = $('#able-vts table');
- $rows = $table.find('tr');
-
- numRows = $rows.length - 1; // exclude header row
-
- newRowNum = parseInt(rowNum) + 1;
- newRowId = 'able-vts-row-' + newRowNum;
-
- // Create an empty row
- $tr = $('',{
- 'id': newRowId
- });
-
- // Row #
- $td = $('').text(newRowNum);
- $tr.append($td);
-
- // Kind (add a select field for chosing a kind)
- newKind = null;
- $select = $('',{
- 'id': 'able-vts-kind-' + newRowNum,
- 'aria-label': 'What kind of track is this?',
- 'placeholder': 'Select a kind'
- }).on('change',function() {
- newKind = $(this).val();
- newClass = 'kind-' + newKind;
- $parentRow = $(this).closest('tr');
- // replace the select field with the chosen value as text
- $(this).parent().text(newKind);
- // add a class to the parent row
- $parentRow.addClass(newClass);
- });
- options = ['','captions','chapters','descriptions','subtitles'];
- for (i=0; i',{
- 'value': options[i]
- }).text(options[i]);
- $select.append($option);
- }
- $td = $('').append($select);
- $tr.append($td);
-
- // Start
- $td = $(' ',{
- 'contenteditable': 'true'
- }); // TODO; Intelligently assign a new start time (see getAdjustedTimes())
- $tr.append($td);
-
- // End
- $td = $(' ',{
- 'contenteditable': 'true'
- }); // TODO; Intelligently assign a new end time (see getAdjustedTimes())
- $tr.append($td);
-
- // Content
- $td = $(' ',{
- 'contenteditable': 'true'
- });
- $tr.append($td);
-
- // Actions
- $td = this.addVtsActionButtons(newRowNum,numRows);
- $tr.append($td);
-
- // Now insert the new row
- $table.find('tr').eq(rowNum).after($tr);
-
- // Update row.id, Row # cell, & action items for all rows after the inserted one
- for (i=newRowNum; i <= numRows; i++) {
- nextRowNum = i + 1;
- $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id
- $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td
- $buttons = $rows.eq(i).find('button');
- this.updateVtsActionButtons($buttons,nextRowNum);
- }
-
- // Auto-adjust times
- this.adjustTimes(newRowNum);
-
- // Announce the insertion
- this.showVtsAlert('A new row ' + newRowNum + ' has been inserted'); // TODO: Localize this
-
- // Place focus in new select field
- $select.focus();
-
- };
-
- AblePlayer.prototype.deleteRow = function(rowNum) {
-
- var $table, $rows, numRows, i, nextRowNum, $buttons;
-
- $table = $('#able-vts table');
- $table[0].deleteRow(rowNum);
- $rows = $table.find('tr'); // this does not include the deleted row
- numRows = $rows.length - 1; // exclude header row
-
- // Update row.id, Row # cell, & action buttons for all rows after the deleted one
- for (i=rowNum; i <= numRows; i++) {
- nextRowNum = i;
- $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id
- $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td
- $buttons = $rows.eq(i).find('button');
- this.updateVtsActionButtons($buttons,nextRowNum);
- }
-
- // Announce the deletion
- this.showVtsAlert('Row ' + rowNum + ' has been deleted'); // TODO: Localize this
-
- };
-
- AblePlayer.prototype.moveRow = function(rowNum,direction) {
-
- // swap two rows
- var $rows, $thisRow, otherRowNum, $otherRow, newTimes, msg;
-
- $rows = $('#able-vts table').find('tr');
- $thisRow = $('#able-vts table').find('tr').eq(rowNum);
- if (direction == 'up') {
- otherRowNum = parseInt(rowNum) - 1;
- $otherRow = $('#able-vts table').find('tr').eq(otherRowNum);
- $otherRow.before($thisRow);
- }
- else if (direction == 'down') {
- otherRowNum = parseInt(rowNum) + 1;
- $otherRow = $('#able-vts table').find('tr').eq(otherRowNum);
- $otherRow.after($thisRow);
- }
- // Update row.id, Row # cell, & action buttons for the two swapped rows
- $thisRow.attr('id','able-vts-row-' + otherRowNum);
- $thisRow.find('td').eq(0).text(otherRowNum);
- this.updateVtsActionButtons($thisRow.find('button'),otherRowNum);
- $otherRow.attr('id','able-vts-row-' + rowNum);
- $otherRow.find('td').eq(0).text(rowNum);
- this.updateVtsActionButtons($otherRow.find('button'),rowNum);
-
- // auto-adjust times
- this.adjustTimes(otherRowNum);
-
- // Announce the move (TODO: Localize this)
- msg = 'Row ' + rowNum + ' has been moved ' + direction;
- msg += ' and is now Row ' + otherRowNum;
- this.showVtsAlert(msg);
- };
-
- AblePlayer.prototype.adjustTimes = function(rowNum) {
-
- // Adjusts start and end times of the current, previous, and next rows in VTS table
- // after a move or insert
- // NOTE: Fully automating this process would be extraordinarily complicated
- // The goal here is simply to make subtle tweaks to ensure rows appear
- // in the new order within the Able Player transcript
- // Additional tweaking will likely be required by the user
-
- // HISTORY: Originally set minDuration to 2 seconds for captions and .500 for descriptions
- // However, this can results in significant changes to existing caption timing,
- // with not-so-positive results.
- // As of 3.1.15, setting minDuration to .001 for all track kinds
- // Users will have to make further adjustments manually if needed
-
- // TODO: Add WebVTT validation on save, since tweaking times is risky
-
- var minDuration, $rows, prevRowNum, nextRowNum, $row, $prevRow, $nextRow,
- kind, prevKind, nextKind,
- start, prevStart, nextStart,
- end, prevEnd, nextEnd;
-
- // Define minimum duration (in seconds) for each kind of track
- minDuration = [];
- minDuration['captions'] = .001;
- minDuration['descriptions'] = .001;
- minDuration['chapters'] = .001;
-
- // refresh rows object
- $rows = $('#able-vts table').find('tr');
-
- // Get kind, start, and end from current row
- $row = $rows.eq(rowNum);
- if ($row.is('[class^="kind-"]')) {
- // row has a class that starts with "kind-"
- // Extract kind from the class name
- kind = this.getKindFromClass($row.attr('class'));
- }
- else {
- // Kind has not been assigned (e.g., newly inserted row)
- // Set as captions row by default
- kind = 'captions';
- }
- start = this.getSecondsFromColonTime($row.find('td').eq(2).text());
- end = this.getSecondsFromColonTime($row.find('td').eq(3).text());
-
- // Get kind, start, and end from previous row
- if (rowNum > 1) {
- // this is not the first row. Include the previous row
- prevRowNum = rowNum - 1;
- $prevRow = $rows.eq(prevRowNum);
- if ($prevRow.is('[class^="kind-"]')) {
- // row has a class that starts with "kind-"
- // Extract kind from the class name
- prevKind = this.getKindFromClass($prevRow.attr('class'));
- }
- else {
- // Kind has not been assigned (e.g., newly inserted row)
- prevKind = null;
- }
- prevStart = this.getSecondsFromColonTime($prevRow.find('td').eq(2).text());
- prevEnd = this.getSecondsFromColonTime($prevRow.find('td').eq(3).text());
- }
- else {
- // this is the first row
- prevRowNum = null;
- $prevRow = null;
- prevKind = null;
- prevStart = null;
- prevEnd = null;
- }
-
- // Get kind, start, and end from next row
- if (rowNum < ($rows.length - 1)) {
- // this is not the last row. Include the next row
- nextRowNum = rowNum + 1;
- $nextRow = $rows.eq(nextRowNum);
- if ($nextRow.is('[class^="kind-"]')) {
- // row has a class that starts with "kind-"
- // Extract kind from the class name
- nextKind = this.getKindFromClass($nextRow.attr('class'));
- }
- else {
- // Kind has not been assigned (e.g., newly inserted row)
- nextKind = null;
- }
- nextStart = this.getSecondsFromColonTime($nextRow.find('td').eq(2).text());
- nextEnd = this.getSecondsFromColonTime($nextRow.find('td').eq(3).text());
- }
- else {
- // this is the last row
- nextRowNum = null;
- $nextRow = null;
- nextKind = null;
- nextStart = null;
- nextEnd = null;
- }
-
- if (isNaN(start)) {
- if (prevKind == null) {
- // The previous row was probably inserted, and user has not yet selected a kind
- // automatically set it to captions
- prevKind = 'captions';
- $prevRow.attr('class','kind-captions');
- $prevRow.find('td').eq(1).html('captions');
- }
- // Current row has no start time (i.e., it's an inserted row)
- if (prevKind === 'captions') {
- // start the new row immediately after the captions end
- start = (parseFloat(prevEnd) + .001).toFixed(3);
- if (nextStart) {
- // end the new row immediately before the next row starts
- end = (parseFloat(nextStart) - .001).toFixed(3);
- }
- else {
- // this is the last row. Use minDuration to calculate end time.
- end = (parseFloat(start) + minDuration[kind]).toFixed(3);
- }
- }
- else if (prevKind === 'chapters') {
- // start the new row immediately after the chapter start (not end)
- start = (parseFloat(prevStart) + .001).toFixed(3);
- if (nextStart) {
- // end the new row immediately before the next row starts
- end = (parseFloat(nextStart) - .001).toFixed(3);
- }
- else {
- // this is the last row. Use minDuration to calculate end time.
- end = (parseFloat(start) + minDurartion[kind]).toFixed(3);
- }
- }
- else if (prevKind === 'descriptions') {
- // start the new row minDuration['descriptions'] after the description starts
- // this will theoretically allow at least a small cushion for the description to be read
- start = (parseFloat(prevStart) + minDuration['descriptions']).toFixed(3);
- end = (parseFloat(start) + minDuration['descriptions']).toFixed(3);
- }
- }
- else {
- // current row has a start time (i.e., an existing row has been moved))
- if (prevStart) {
- // this is not the first row.
- if (prevStart < start) {
- if (start < nextStart) {
- // No change is necessary
- }
- else {
- // nextStart needs to be incremented
- nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3);
- nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3);
- // TODO: Ensure nextEnd does not exceed the following start (nextNextStart)
- // Or... maybe this is getting too complicated and should be left up to the user
- }
- }
- else {
- // start needs to be incremented
- start = (parseFloat(prevStart) + minDuration[prevKind]).toFixed(3);
- end = (parseFloat(start) + minDuration[kind]).toFixed(3);
- }
- }
- else {
- // this is the first row
- if (start < nextStart) {
- // No change is necessary
- }
- else {
- // nextStart needs to be incremented
- nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3);
- nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3);
- }
- }
- }
-
- // check to be sure there is sufficient duration between new start & end times
- if (end - start < minDuration[kind]) {
- // duration is too short. Change end time
- end = (parseFloat(start) + minDuration[kind]).toFixed(3);
- if (nextStart) {
- // this is not the last row
- // increase start time of next row
- nextStart = (parseFloat(end) + .001).toFixed(3);
- }
- }
-
- // Update all affected start/end times
- $row.find('td').eq(2).text(this.formatSecondsAsColonTime(start,true));
- $row.find('td').eq(3).text(this.formatSecondsAsColonTime(end,true));
- if ($prevRow) {
- $prevRow.find('td').eq(2).text(this.formatSecondsAsColonTime(prevStart,true));
- $prevRow.find('td').eq(3).text(this.formatSecondsAsColonTime(prevEnd,true));
- }
- if ($nextRow) {
- $nextRow.find('td').eq(2).text(this.formatSecondsAsColonTime(nextStart,true));
- $nextRow.find('td').eq(3).text(this.formatSecondsAsColonTime(nextEnd,true));
- }
- };
-
- AblePlayer.prototype.getKindFromClass = function(myclass) {
-
- // This function is called when a class with prefix "kind-" is found in the class attribute
- // TODO: Rewrite this using regular expressions
- var kindStart, kindEnd, kindLength, kind;
-
- kindStart = myclass.indexOf('kind-')+5;
- kindEnd = myclass.indexOf(' ',kindStart);
- if (kindEnd == -1) {
- // no spaces found, "kind-" must be the only myclass
- kindLength = myclass.length - kindStart;
- }
- else {
- kindLength = kindEnd - kindStart;
- }
- kind = myclass.substr(kindStart,kindLength);
- return kind;
- };
-
- AblePlayer.prototype.showVtsAlert = function(message) {
-
- // this is distinct from greater Able Player showAlert()
- // because it's positioning needs are unique
- // For now, alertDiv is fixed at top left of screen
- // but could ultimately be modified to appear near the point of action in the VTS table
- this.$vtsAlert.text(message).show().delay(3000).fadeOut('slow');
- };
-
- AblePlayer.prototype.parseVtsOutput = function($table) {
-
- // parse table into arrays, then into WebVTT content, for each kind
- // Display the WebVTT content in textarea fields for users to copy and paste
- var lang, i, kinds, kind, vtt, $rows, start, end, content, $output;
-
- lang = $table.attr('lang');
- kinds = ['captions','chapters','descriptions','subtitles'];
- vtt = {};
- for (i=0; i < kinds.length; i++) {
- kind = kinds[i];
- vtt[kind] = 'WEBVTT' + "\n\n";
- }
- $rows = $table.find('tr');
- if ($rows.length > 0) {
- for (i=0; i < $rows.length; i++) {
- kind = $rows.eq(i).find('td').eq(1).text();
- if ($.inArray(kind,kinds) !== -1) {
- start = $rows.eq(i).find('td').eq(2).text();
- end = $rows.eq(i).find('td').eq(3).text();
- content = $rows.eq(i).find('td').eq(4).text();
- if (start !== undefined && end !== undefined) {
- vtt[kind] += start + ' --> ' + end + "\n";
- if (content !== 'undefined') {
- vtt[kind] += content;
- }
- vtt[kind] += "\n\n";
- }
- }
- }
- }
- $output = $('',{
- 'id': 'able-vts-output'
- })
- $('#able-vts').append($output);
- for (i=0; i < kinds.length; i++) {
- kind = kinds[i];
- if (vtt[kind].length > 8) {
- // some content has been added
- this.showWebVttOutput(kind,vtt[kind],lang)
- }
- }
- };
-
- AblePlayer.prototype.showWebVttOutput = function(kind,vttString,lang) {
-
- var $heading, filename, $p, pText, $textarea;
-
- $heading = $('
').text(kind.charAt(0).toUpperCase() + kind.slice(1));
- filename = this.getFilenameFromTracks(kind,lang);
- pText = 'If you made changes, copy/paste the following content ';
- if (filename) {
- pText += 'to replace the original content of your ' + this.getLanguageName(lang) + ' ';
- pText += '' + kind + ' WebVTT file (' + filename + ' ).';
- }
- else {
- pText += 'into a new ' + this.getLanguageName(lang) + ' ' + kind + ' WebVTT file.';
- }
- $p = $(' ',{
- 'class': 'able-vts-output-instructions'
- }).html(pText);
- $textarea = $('