|
115 | 115 | this.lyricsMode = true; |
116 | 116 | } |
117 | 117 |
|
118 | | - if ($(media).data('transcript-title') !== undefined) { |
| 118 | + if ($(media).data('chapters-div') !== undefined && $(media).data('chapters-div') !== "") { |
| 119 | + this.chaptersDivLocation = $(media).data('chapters-div'); |
| 120 | + } |
| 121 | + |
| 122 | + if ($(media).data('chapters-title') !== undefined) { |
119 | 123 | // NOTE: empty string is valid; results in no title being displayed |
120 | | - this.transcriptTitle = $(media).data('transcript-title'); |
| 124 | + this.chaptersTitle = $(media).data('chapters-title'); |
| 125 | + } |
| 126 | + |
| 127 | + if ($(media).data('chapters-default') !== undefined && $(media).data('chapters-default') !== "") { |
| 128 | + this.defaultChapter = $(media).data('chapters-default'); |
| 129 | + this.chapter = this.defaultChapter; // this.chapter is the id of the default chapter (as defined within WebVTT file) |
| 130 | + } |
| 131 | + |
| 132 | + if ($(media).data('use-chapters-button') !== undefined && $(media).data('use-chapters-button') === false) { |
| 133 | + this.useChaptersButton = false; |
121 | 134 | } |
122 | 135 |
|
123 | 136 | if ($(media).data('youtube-id') !== undefined && $(media).data('youtube-id') !== "") { |
|
348 | 361 | // it might be desirable for the transcript to always be ON, with no toggle |
349 | 362 | // This can be overridden with data-transcript-button="false" |
350 | 363 | this.useTranscriptButton = true; |
| 364 | + |
| 365 | + // useChaptersButton - on by default if there's a track with kind="chapters" |
| 366 | + // However, if chapters is written to an external div via data-chapters-div |
| 367 | + // it might be desirable for the chapters to always be ON, with no toggle |
| 368 | + // This can be overridden with data-chapters-button="false" |
| 369 | + this.useChaptersButton = true; |
351 | 370 |
|
352 | 371 | this.playing = false; // will change to true after 'playing' event is triggered |
353 | 372 |
|
|
596 | 615 | thisObj.updateCaption(); |
597 | 616 | thisObj.updateTranscript(); |
598 | 617 | thisObj.showSearchResults(); |
| 618 | + if (thisObj.defaultChapter) { |
| 619 | + thisObj.seekToDefaultChapter(); |
| 620 | + } |
599 | 621 | }); |
600 | 622 | }); |
601 | 623 | }; |
|
1736 | 1758 | (function ($) { |
1737 | 1759 | // See section 4.1 of dev.w3.org/html5/webvtt for format details. |
1738 | 1760 | AblePlayer.prototype.parseWebVTT = function(srcFile,text) { |
| 1761 | + |
1739 | 1762 | // Normalize line ends to \n. |
1740 | 1763 | text = text.replace(/(\r\n|\n|\r)/g,'\n'); |
1741 | 1764 |
|
|
1933 | 1956 | } |
1934 | 1957 |
|
1935 | 1958 | function parseCue(state) { |
| 1959 | + |
1936 | 1960 | var nextLine = peekLine(state); |
1937 | 1961 | var cueId; |
1938 | 1962 | var errString; |
|
1981 | 2005 | settings: cueSettings, |
1982 | 2006 | components: components |
1983 | 2007 | }); |
1984 | | -} |
1985 | | - |
| 2008 | + } |
| 2009 | + |
1986 | 2010 | function getCueSettings(state) { |
1987 | 2011 | var cueSettings = {}; |
1988 | 2012 | while (state.text.length > 0 && state.text[0] !== '\n') { |
|
1993 | 2017 | return cueSettings; |
1994 | 2018 | } |
1995 | 2019 |
|
1996 | | - |
1997 | 2020 | function getCuePayload(state) { |
1998 | 2021 | // Parser based on instructions in draft. |
1999 | 2022 | var result = {type: 'internal', tagName: '', value: '', classes: [], annotation: '', parent: null, children: [], language: ''}; |
|
2544 | 2567 | if (this.includeTranscript) { |
2545 | 2568 | this.injectTranscriptArea(); |
2546 | 2569 | this.addTranscriptAreaEvents(); |
2547 | | - } |
2548 | | - |
| 2570 | + } |
2549 | 2571 | this.injectAlert(); |
2550 | 2572 | this.injectPlaylist(); |
2551 | 2573 | }; |
2552 | 2574 |
|
2553 | 2575 | AblePlayer.prototype.injectOffscreenHeading = function () { |
2554 | 2576 | // Add offscreen heading to the media container. |
2555 | | - // To fine the nearest heading in the ancestor tree, |
2556 | | - // loop over each parent of $ableDiv until a heading is found |
2557 | | - // If multiple headings are found beneath a given parent, get the closest |
2558 | | - // The heading injected in $ableDiv is one level deeper than the closest heading |
2559 | | - var headingType; |
2560 | | - |
2561 | | - var $parents = this.$ableDiv.parents(); |
2562 | | - $parents.each(function(){ |
2563 | | - var $this = $(this); |
2564 | | - var $thisHeadings = $this.find('h1, h2, h3, h4, h5, h6'); |
2565 | | - var numHeadings = $thisHeadings.length; |
2566 | | - if(numHeadings){ |
2567 | | - headingType = $thisHeadings.eq(numHeadings-1).prop('tagName'); |
2568 | | - return false; |
2569 | | - } |
2570 | | - }); |
2571 | | - if (typeof headingType === 'undefined') { |
2572 | | - var headingType = 'h1'; |
2573 | | - } |
2574 | | - else { |
2575 | | - // Increment closest heading by one if less than 6. |
2576 | | - var headingNumber = parseInt(headingType[1]); |
2577 | | - headingNumber += 1; |
2578 | | - if (headingNumber > 6) { |
2579 | | - headingNumber = 6; |
2580 | | - } |
2581 | | - headingType = 'h' + headingNumber.toString(); |
2582 | | - } |
2583 | | - this.playerHeadingLevel = headingNumber; |
| 2577 | + // The heading injected in $ableDiv is one level deeper than the closest parent heading |
| 2578 | + // as determined by getNextHeadingLevel() |
| 2579 | + var headingType; |
| 2580 | + this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6 |
| 2581 | + headingType = 'h' + this.playerHeadingLevel.toString(); |
2584 | 2582 | this.$headingDiv = $('<' + headingType + '>'); |
2585 | 2583 | this.$ableDiv.prepend(this.$headingDiv); |
2586 | 2584 | this.$headingDiv.addClass('able-offscreen'); |
2587 | | - this.$headingDiv.text(this.tt.playerHeading); |
2588 | | - |
| 2585 | + this.$headingDiv.text(this.tt.playerHeading); |
2589 | 2586 | }; |
2590 | 2587 |
|
2591 | 2588 | AblePlayer.prototype.injectBigPlayButton = function () { |
|
2717 | 2714 | } |
2718 | 2715 | }; |
2719 | 2716 |
|
| 2717 | + AblePlayer.prototype.populateChaptersDiv = function() { |
| 2718 | + |
| 2719 | + var thisObj, headingLevel, headingType, headingId, $chaptersHeading, |
| 2720 | + $chaptersNav, $chaptersList, $chapterItem, $chapterButton, |
| 2721 | + i, itemId, chapter, buttonId, hasDefault, |
| 2722 | + getFocusFunction, getHoverFunction, getBlurFunction, getClickFunction, |
| 2723 | + $thisButton, $thisListItem, $prevButton, $nextButton, blurListener; |
| 2724 | + |
| 2725 | + thisObj = this; |
| 2726 | + |
| 2727 | + if ($('#' + this.chaptersDivLocation)) { |
| 2728 | + this.$chaptersDiv = $('#' + this.chaptersDivLocation); |
| 2729 | + this.$chaptersDiv.addClass('able-chapters-div'); |
| 2730 | + |
| 2731 | + // add optional header |
| 2732 | + if (this.chaptersTitle) { |
| 2733 | + headingLevel = this.getNextHeadingLevel(this.$chaptersDiv); |
| 2734 | + headingType = 'h' + headingLevel.toString(); |
| 2735 | + headingId = this.mediaId + '-chapters-heading'; |
| 2736 | + $chaptersHeading = $('<' + headingType + '>', { |
| 2737 | + 'class': 'able-chapters-heading', |
| 2738 | + 'id': headingId |
| 2739 | + }).text(this.chaptersTitle); |
| 2740 | + this.$chaptersDiv.append($chaptersHeading); |
| 2741 | + } |
| 2742 | + |
| 2743 | + $chaptersNav = $('<nav>'); |
| 2744 | + if (this.chaptersTitle) { |
| 2745 | + $chaptersNav.attr('aria-labeledby',headingId); |
| 2746 | + } |
| 2747 | + else { |
| 2748 | + $chaptersNav.attr('aria-label',this.tt.chapters); |
| 2749 | + } |
| 2750 | + |
| 2751 | + $chaptersList = $('<ul>'); |
| 2752 | + for (i in this.chapters) { |
| 2753 | + chapter = this.chapters[i]; |
| 2754 | + itemId = this.mediaId + '-chapters-' + i; // TODO: Maybe not needed??? |
| 2755 | + $chapterItem = $('<li></li>'); |
| 2756 | + $chapterButton = $('<button>',{ |
| 2757 | + 'type': 'button', |
| 2758 | + 'val': i |
| 2759 | + }).text(this.flattenCueForCaption(chapter)); |
| 2760 | + |
| 2761 | + // add event listeners |
| 2762 | + getClickFunction = function (time) { |
| 2763 | + return function () { |
| 2764 | + $(this).closest('ul').find('li') |
| 2765 | + .removeClass('able-current-chapter') |
| 2766 | + .attr('aria-selected',''); |
| 2767 | + $(this).closest('li') |
| 2768 | + .addClass('able-current-chapter') |
| 2769 | + .attr('aria-selected','true'); |
| 2770 | + thisObj.seekTo(time); |
| 2771 | + } |
| 2772 | + }; |
| 2773 | + $chapterButton.on('click',getClickFunction(chapter.start)); // works with Enter too |
| 2774 | + $chapterButton.on('focus',function() { |
| 2775 | + $(this).closest('ul').find('li').removeClass('able-focus'); |
| 2776 | + $(this).closest('li').addClass('able-focus'); |
| 2777 | + }); |
| 2778 | + $chapterItem.on('hover',function() { |
| 2779 | + $(this).closest('ul').find('li').removeClass('able-focus'); |
| 2780 | + $(this).addClass('able-focus'); |
| 2781 | + }); |
| 2782 | + $chapterItem.on('mouseleave',function() { |
| 2783 | + $(this).removeClass('able-focus'); |
| 2784 | + }); |
| 2785 | + $chapterButton.on('blur',function() { |
| 2786 | + $(this).closest('li').removeClass('able-focus'); |
| 2787 | + }); |
| 2788 | + |
| 2789 | + // put it all together |
| 2790 | + $chapterItem.append($chapterButton); |
| 2791 | + $chaptersList.append($chapterItem); |
| 2792 | + if (this.defaultChapter == chapter.id) { |
| 2793 | + $chapterButton.attr('aria-selected','true').parent('li').addClass('able-current-chapter'); |
| 2794 | + hasDefault = true; |
| 2795 | + } |
| 2796 | + } |
| 2797 | + } |
| 2798 | + if (!hasDefault) { |
| 2799 | + // select the first button |
| 2800 | + $chaptersList.find('button').first().attr('aria-selected','true') |
| 2801 | + .parent('li').addClass('able-current-chapter'); |
| 2802 | + } |
| 2803 | + $chaptersNav.append($chaptersList); |
| 2804 | + this.$chaptersDiv.append($chaptersNav); |
| 2805 | + }; |
| 2806 | + |
2720 | 2807 | AblePlayer.prototype.splitPlayerIntoColumns = function (feature) { |
2721 | 2808 | // feature is either 'transcript' or 'sign' |
2722 | 2809 | // if present, player is split into two column, with this feature in the right column |
|
2918 | 3005 |
|
2919 | 3006 | // Create and fill in the popup menu forms for various controls. |
2920 | 3007 | AblePlayer.prototype.setupPopups = function () { |
2921 | | - |
2922 | 3008 | var popups, thisObj, hasDefault, i, j, |
2923 | 3009 | tracks, trackList, trackItem, track, |
2924 | 3010 | radioName, radioId, trackButton, trackLabel, |
|
2940 | 3026 | if (this.captions.length > 0) { |
2941 | 3027 | popups.push('captions'); |
2942 | 3028 | } |
2943 | | - if (this.chapters.length > 0) { |
| 3029 | + if (this.chapters.length > 0 && this.useChaptersButton) { |
2944 | 3030 | popups.push('chapters'); |
2945 | 3031 | } |
2946 | 3032 | } |
|
3239 | 3325 | bll.push('transcript'); |
3240 | 3326 | } |
3241 | 3327 |
|
3242 | | - if (this.mediaType === 'video' && this.hasChapters) { |
| 3328 | + if (this.mediaType === 'video' && this.hasChapters && this.useChaptersButton) { |
3243 | 3329 | bll.push('chapters'); |
3244 | 3330 | } |
3245 | 3331 |
|
|
3966 | 4052 | AblePlayer.prototype.setupChapters = function (track, cues) { |
3967 | 4053 | // NOTE: WebVTT supports nested timestamps (to form an outline) |
3968 | 4054 | // This is not currently supported. |
| 4055 | + var i=0; |
3969 | 4056 | this.hasChapters = true; |
3970 | | - this.chapters = cues; |
| 4057 | + this.chapters = cues; |
| 4058 | + if (this.chaptersDivLocation) { |
| 4059 | + this.populateChaptersDiv(); |
| 4060 | + } |
3971 | 4061 | }; |
3972 | 4062 |
|
| 4063 | + AblePlayer.prototype.seekToDefaultChapter = function() { |
| 4064 | + // this function is only called if this.defaultChapter is not null |
| 4065 | + // step through chapters looking for default |
| 4066 | + var i=0; |
| 4067 | + while (i < this.chapters.length) { |
| 4068 | + if (this.chapters[i].id === this.defaultChapter) { |
| 4069 | + // found the default chapter! Seek to it |
| 4070 | + this.seekTo(this.chapters[i].start); |
| 4071 | + } |
| 4072 | + i++; |
| 4073 | + } |
| 4074 | + }; |
| 4075 | + |
3973 | 4076 | AblePlayer.prototype.setupMetadata = function(track, cues) { |
3974 | 4077 | // NOTE: Metadata is currently only supported if data-meta-div is provided |
3975 | 4078 | // The player does not display metadata internally |
|
5277 | 5380 | })(jQuery); |
5278 | 5381 |
|
5279 | 5382 | (function ($) { |
| 5383 | + |
| 5384 | + AblePlayer.prototype.getNextHeadingLevel = function($element) { |
| 5385 | + |
| 5386 | + // Finds the nearest heading in the ancestor tree |
| 5387 | + // Loops over each parent of the current element until a heading is found |
| 5388 | + // If multiple headings are found beneath a given parent, get the closest |
| 5389 | + // Returns an integer (1-6) representing the next available heading level |
| 5390 | + |
| 5391 | + var $parents, $foundHeadings, numHeadings, headingType, headingNumber; |
| 5392 | + |
| 5393 | + $parents = $element.parents(); |
| 5394 | + $parents.each(function(){ |
| 5395 | + $foundHeadings = $(this).find('h1, h2, h3, h4, h5, h6'); |
| 5396 | + numHeadings = $foundHeadings.length; |
| 5397 | + if (numHeadings) { |
| 5398 | + headingType = $foundHeadings.eq(numHeadings-1).prop('tagName'); |
| 5399 | + return false; |
| 5400 | + } |
| 5401 | + }); |
| 5402 | + if (typeof headingType === 'undefined') { |
| 5403 | + // page has no headings |
| 5404 | + headingNumber = 1; |
| 5405 | + } |
| 5406 | + else { |
| 5407 | + // Increment closest heading by one if less than 6. |
| 5408 | + headingNumber = parseInt(headingType[1]); |
| 5409 | + headingNumber += 1; |
| 5410 | + if (headingNumber > 6) { |
| 5411 | + headingNumber = 6; |
| 5412 | + } |
| 5413 | + } |
| 5414 | + return headingNumber; |
| 5415 | + }; |
| 5416 | + |
5280 | 5417 | AblePlayer.prototype.countProperties = function(obj) { |
5281 | 5418 | // returns the number of properties in an object |
5282 | 5419 | var count, prop; |
|
0 commit comments