From ebc90562bb6a00601e42498297ffe559020e539c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 8 Jul 2025 17:28:55 +0100 Subject: [PATCH 001/171] VTT parsing - Allow hour section that isn't 2 digits - Allow more than 2 digits for extremely long videos - Although the spec says it must be 2+ and so single digit times (e.g. 1:02:24.000 --> 1:04:48.000) are invalid, allow these too as it's unambiguous what was intended and easy to handle --- scripts/webvtt.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/webvtt.js b/scripts/webvtt.js index 8483abe9..616e6159 100644 --- a/scripts/webvtt.js +++ b/scripts/webvtt.js @@ -727,7 +727,9 @@ } var timestamp = cut(state, nextSpace); - var results = /((\d\d):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(timestamp); + // The spec requires exactly 2 characters for minutes and seconds, and 2+ for hours, + // but some VTT generation creates 1 digit hour times (e.g. "1:02:24.000 --> 1:04:48.000") and it seems harmless to allow that here + var results = /((\d+):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(timestamp); if (!results) { state.error = 'Unable to parse timestamp'; From 96210303776b53034437b294d789d81e092963e4 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 20 Jul 2025 20:59:06 -0500 Subject: [PATCH 002/171] Skin documentation is incorrect. --- scripts/ableplayer-base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ableplayer-base.js b/scripts/ableplayer-base.js index e888b5c1..70f55ae8 100644 --- a/scripts/ableplayer-base.js +++ b/scripts/ableplayer-base.js @@ -384,7 +384,7 @@ var AblePlayerInstances = []; // Skin // valid values of data-skin are: - // '2020' (default as of 5.0), all buttons in one row beneath a full-width seekbar + // '2020' (default as of 4.6), all buttons in one row beneath a full-width seekbar // 'legacy', two rows of controls; seekbar positioned in available space within top row if ($(media).data('skin') == 'legacy') { this.skin = 'legacy'; From 918574189d6a6c441b036d7198487d85a6ddb273 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 20 Jul 2025 23:12:37 -0500 Subject: [PATCH 003/171] Initial draft of new stylesheet This makes some colors more consistent, fixes a handful of missing hover/focus states, removes floats and replaces with grid/flex, and evens out a number of design characteristics. See #647 --- styles/base.css | 1253 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1253 insertions(+) create mode 100644 styles/base.css diff --git a/styles/base.css b/styles/base.css new file mode 100644 index 00000000..86456a5f --- /dev/null +++ b/styles/base.css @@ -0,0 +1,1253 @@ +/* + Able Player core styles + + Default z-index map: + + * Modal dialog + div.able-modal-dialog = 10000 + div.able-modal-overlay = 9500 + + * Items that should always be on top (9000): + #able-vts = 9400 + .able-alert = 9400 + .able-window-toolbar .able-button-handler-preferences = 9300 + .able-popup = 9200 + .able-volume-head = 9175 + .able-volume-slider = 9150 + .able-tooltip = 9000 + + * Pop-ups with critical content: (7000 - 8000): + .able-sign-window = 8000 + .able-transcript-area = 7000 + + * Player controls: (5000 - 6000) + .able-controller .able-seekbar = 6900 + .able-controller .buttonOff = 6800 + .able-controller div[role="button"] > img = 6700 + .able-controller div[role="button"] > span = 6700 + .able-controller div[role="button"] = 6600 + .able-big-play-button = 6500 + div.able-captions-wrapper = 6000 + .able-seekbar-head = 5500 + .able-seekbar-played = 5200 + .able-seekbar-loaded = 5100 + .able = 5000 +*/ +.able-wrapper, .able-wrapper * { + box-sizing: border-box; +} + +.able-wrapper { + position: relative; + margin: 0 auto; + padding: 0; + max-width: 100%; + height: auto; + text-align: start; +} + +.able { + position: relative; + margin: 0; + padding: 0; + width: 100%; /* will be changed dynamically as player is constructed */ + background-color: #000; + z-index: 5000; +} + +.able-player-transcript .able-window-toolbar input, +.able-wrapper .able input { + margin: 0; + padding: 2px 4px; +} + +.able-control-row { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + align-items: center; + padding: 4px 8px; +} + +.able .able-vidcap-container { + left: 0; + margin: 0; + position: relative; + top: 0; +} + +.able .able-audcap-container { + position: relative; + margin: 0; + padding: .5rem; +} + +.able-player { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + background-color: #262626; /* background color of player (appears on top & bottom) */ +} + +/* Screen reader hidden text classes */ +.able-transcript .able-hidden, +.able-clipped, .able-screenreader-alert, .able-offscreen { + border: 0; + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} + +.able-media-container audio { + display: none !important; +} + +.able-controller { + position: relative; + background-color: #464646; /* background color of controller bar */ + padding: 0; +} + +.able-poster { + position: absolute; + top: 0; + left: 0; + width: 100% !important; + height: auto !important; +} + +.able .able-vidcap-container { + overflow: visible; +} + +.able .able-vidcap-container video { + max-width: 100%; + display: block; /* Prevents excess height after element */ +} + +/* + YouTube and Vimeo +*/ +.able-media-container iframe { + max-width: 100% !important; +} + +/* + Controller Buttons & Controls +*/ +.able-wrapper .able button.able-big-play-button { + position: absolute; + color: #fff; + background-color: transparent; + border: none; + outline: none; + left: 0; + top: 0; + padding: 0; + z-index: 6500; + opacity: 0.75; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; +} + +.able-big-play-button:hover, .able-big-play-button:focus { + opacity: 100; +} + +.able-big-play-button svg { + background-color: #000; + padding: 1rem; + width: 40%; + height: 40%; + min-width: 140px; + min-height: 140px; + border-radius: 5px; +} + +.able-big-play-button:hover .icon-play, +.able-big-play-button:hover svg { + outline: 3px solid #fff; +} + +.able-big-play-button:focus .icon-play, +.able-big-play-button:focus svg { + outline: 3px solid #fff; +} + +.able-left-controls, .able-right-controls { + overflow: visible; + display: flex; + gap: 3px; + align-items: center; +} + +.able-black-controls, +.able-black-controls button, +.able-black-controls div[role="button"], +.able-black-controls label { + color: #000 !important; +} + +.able-black-controls .able-seekbar { + border: 1px solid #000; +} + +.able-white-controls, +.able-white-controls button, +.able-white-controls div[role="button"], +.able-black-controls label { + color: #fff !important; +} + +.able-white-controls .able-seekbar { + border: 1px solid #aaa; +} + +.able-controller div[role="button"] { + background: none; + position: relative; + display: inline-block; + border-style: none; + padding: 2px; + min-width: 24px; + height: 24px; + border: none; + overflow: visible !important; + display: flex; + justify-content: center; + align-items: center; + z-index: 6600; +} + +.able-controller .buttonOff { + opacity: 0.5; + z-index: 6800; +} + +.able-controller .able-seekbar { + z-index: 6900; +} + +.able-controller div[role="button"]:hover, +.able-controller div[role="button"]:focus { + outline-style: solid; + outline-width: medium; +} + +/* Seekbar */ +.able-seekbar-wrapper { + display: block; + width: 100%; + padding: 8px; +} + +.able-seekbar { + display: flex; + align-items: center; + position: relative; + height: 0.5rem; + border: 1px solid; + background-color: #000; +} + +.able-seekbar-loaded { + display: inline-block; + position: absolute; + left: 0; + top: 0; + height: 0.4rem; + background-color: #464646; + z-index: 5100; +} + +.able-seekbar-played { + display: inline-block; + position: absolute; + left: 0; + top: 0; + height: 0.4rem; + background-color: #dadada; + z-index: 5200; +} + +.able-seekbar-head { + display: inline-block; + position: relative; + left: 0; + background-color: #fdfdfd; + width: 1rem; + height: 1rem; + border: 1px solid; + border-radius: 1rem; + z-index: 5500; +} + +/* Volume Slider */ +.able-volume-slider { + height: 120px; + background-color: #000; + outline: 1px solid #656565; + margin: 0; + padding: 8px; + position: absolute; + right: 0px; + bottom: 30px; + display: block; + z-index: 9100; +} + +.able-volume-help { /* not visible; used in aria-describedby */ + display: none; +} + +.able-volume-slider input[type="range"] { + appearance: slider-vertical; + writing-mode: bt-rl; + /* -webkit-appearance: none; */ /* Hides the slider so that custom slider can be made */ + width: 28px; + height: 100%; + background: transparent; /* Otherwise white in Chrome */ +} + +/* + Style slider in Firefox +*/ +.able-volume-slider input[type=range]::-moz-range-track { + border: 1px solid #fff; + width: 7px; + cursor: pointer; + background: #000; +} + +input[type=range]::-moz-range-thumb { + background-color: #fdfdfd; + outline: 1px solid #333; + height: 16px; + width: 24px; + z-index: 9175; +} + +/* + Status Bar +*/ +.able-status-bar { + color: #ccc; + font-size: 0.875rem; + padding: 4px 8px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.able-status-bar span.able-timer { + text-align: left; +} + +.able-status-bar span.able-speed { + text-align: center; +} + +.able-status { + font-style: italic; + text-align :right; +} + +/* + Captions and Descriptions +*/ +div.able-captions-wrapper { + width: 100%; + margin: 0; + padding: 0; + text-align: center; + display: block; + z-index: 6000; +} + +div.able-captions { + display: none; + padding: 0.15em 0.25em; + line-height: 1.35em; + /* settings that are overridden by user prefs */ + background-color: #000; + font-size: 1em; + color: #fff; + opacity: 0.75; +} + +div.able-vidcap-container div.able-captions-overlay { + position: absolute; + margin: 0; + bottom: 0.5em; +} + +div.able-vidcap-container div.able-captions-below { + position: relative; + min-height: 3.2em; +} + +div.able-audcap-container.captions-off { + display: none; +} + +div.able-descriptions { + position: relative; + color: #ff6; /* yellow, to differentiate it from captions */ + background-color: #262626; + min-height: 2.8em; + border-top: 1px solid #666; + margin: 0; + padding: 3%; + width: 94%; + text-align: center; +} + +/* Now Playing */ +div.able-now-playing { + text-align: center; + font-weight: bold; + font-size: 1rem; + color: #fff; + background-color: transparent; + padding: 0.5em 0.5em 1em; +} + +div.able-now-playing span { + font-size: 0.875rem; +} + +div.able-video div.able-now-playing { + display: none; +} + +/* + Modal Dialogs +*/ +div.able-modal-dialog { + position: absolute; + height: auto; + right: 0px; + outline: 0px none; + display: none; + color: #000; + background-color: #fafafa; + z-index: 10000; + max-height: 90%; + overflow: auto; + top: 50%; + left: 50%; + width: 50rem; + max-width: 100%; + margin-left: 0; + margin-right: 0; + transform: translate(-50%,-50%) !important; +} + +div.able-modal-overlay { + position: fixed; + width: 100%; + height: 100%; + background-color: #000; + opacity: 0.5; + margin: 0; + padding: 0; + top: 0; + left: 0; + display: none; + z-index: 9500; +} + +.able-modal-dialog button { + all: unset; + padding: 4px 12px; + font-size: 1.125rem; + border: 2px solid; + margin-right: 5px; +} + +.able-modal-dialog .modalCloseButton { + position: absolute; + top: 5px; + right: 5px; + margin: 0; +} + +div.able-modal-dialog input:hover, +div.able-modal-dialog input:focus, +div.able-modal-dialog button:hover, +div.able-modal-dialog button:focus { + outline-style: solid; + outline-width: 3px; +} + +div.able-modal-dialog h1 { + font-size: 1.5rem; + line-height: 1.6; + margin: 0 0 .5rem; + color: #000; + text-align: center; +} + +.able-prefs-form, +.able-help-div, +.able-resize-form { + background-color: #f5f5f5; + border: medium solid #ccc; + padding: 0.5em 1em; + margin: 0 0 0 1em; + width: 25em; + display: none; +} + +/* + Preferences Form +*/ +.able-prefs-form div[role="group"] { + padding: 1rem 0; + border: none; +} + +.able-prefs-form h2 { + color: #000; + font-weight: bold; + font-size: 1.1em; +} + +.able-desc-pref-prompt { + font-weight: bold; + font-style: italic; + margin-left: 1em !important; +} + +.able-prefDescFormat > div { + margin-left: 1.5em; +} + +.able-prefs-descriptions > div, +.able-prefs-captions > div { + display: flex; + flex-wrap: wrap; + gap: .5rem; + margin-bottom: .25rem; +} + +.able-prefs-descriptions label, +.able-prefs-captions label { + text-align: right; + width: 10rem; +} + +.able-prefs-checkbox label { + width: auto; +} + +.able-prefs-descriptions select, +.able-prefs-captions select { + width: 10em; +} + +div.able-prefDescPause { + margin-top: 1em; +} + +.able-prefs-form div.able-captions-sample { + padding: 0.5em; + text-align: center; +} + +.able-prefs-form div.able-desc-sample { + padding: 0.5em; + text-align: center; + color: #fff; + background-color: #000; +} + +.able-prefs-form h2 { + margin-top: 0; + margin-bottom: 0.5em; + font-size: 1.1em; +} + +.able-prefs-form ul { + margin-top: 0; +} + +/* + Keyboard Preferences Dialog +*/ +able-prefs-form-keyboard ul { + list-style-type: none; +} + +span.able-modkey-alt, +span.able-modkey-ctrl, +span.able-modkey-shift { + color: #666; + font-style: italic; +} + +span.able-modkey { + font-weight: bold; + color: #000; + font-size: 1.1em; +} + +/* + Resize Window Dialog +*/ +.able-resize-form h1 { + font-size: 1.15em; +} + +.able-resize-form div div { + margin: 1em; +} + +.able-resize-form label { + padding-right: 0.5em; + font-weight: bold; +} + +.able-resize-form input[type="text"] { + font-size: 1em; +} + +.able-resize-form input[readonly] { + color: #aaa; +} + +/* + Drag & Drop +*/ +.able-window-toolbar { + background-color: #464646; + min-height: 15px; + padding: 10px; + border-style: solid; + border-width: 0 0 1px 0; + display: flex; + align-items: center; + justify-content: space-between; +} + +.able-draggable:hover { + cursor: move; +} + +.able-window-toolbar .able-button-handler-preferences { + font-size: 1.5em; + background-color: transparent; + border: none; + outline: none; + padding: 0; + z-index: 9300; +} + +.able-window-toolbar .able-button-handler-preferences:hover, +.able-window-toolbar .able-button-handler-preferences:focus { + outline-style: solid; + outline-width: medium; +} + +.able-window-toolbar .able-button-handler-preferences img { + display: block; +} + +.able-window-toolbar .able-popup { + position: absolute; + cursor: default; + left: 0; + top: 0; + display: block; + border-radius: 0 0 5px 5px; + background: #464646; +} + +.able-drag { + outline: 3px dashed #f90; + outline-offset: 2px; + cursor: move; +} + +.able-resizable { + position: absolute; + width: 20px; + height: 20px; + bottom: 0; + right: 0; + cursor: nwse-resize; +} + +.able-resizable svg line { + stroke: #595959; + stroke-width: 2px; +} + +/* Sign Language Window */ +.able-sign-window { + position: relative; + z-index: 8000; + background: #fff; + border: 1px solid #000; +} + +.able-sign-window video { + width: 100%; + margin-bottom: 1rem; +} + +.able-sign-window:focus { + outline: none; +} + +/* External chapters div */ +div.able-chapters-div { + padding: 0; +} + +div.able-chapters-div .able-chapters-heading { + margin: .5rem; + font-size: 1.125rem; + font-weight: bold; +} + +div.able-chapters-div ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +div.able-chapters-div ul li { + padding: 0; + margin: 0; +} + +div.able-chapters-div button { + width: 100%; + height: 100%; + border: none; + background-color: transparent; + color: #000; + font-size: 1rem; + text-align: start; + padding: 8px; +} + +div.able-chapters-div li.able-current-chapter button { + background-color: #000 !important; + color: #fff !important; +} + +div.able-chapters-div li.able-focus { + background-color: #4c4c4c; +} + +div.able-chapters-div button:focus, +div.able-chapters-div button:hover { + border: 0; + outline: none; + color: #fff !important; +} + +div.able-wrapper.fullscreen { + margin: 0 !important; + position: fixed !important; + top: 0 !important; + background: none !important; +} + +/* + Tooltips & Alerts +*/ +.able-tooltip, .able-alert { + position: absolute; + padding: 5px 10px; + border-color: #000; + border-width: 1px; + color: #000 !important; + background-color: #ccc; + border-radius: 5px; + display: block; +} + +.able-alert { + background-color: #ffc; + box-shadow: 0px 0px 16px #262626; + z-index: 9400; + position: absolute; + top: 1em; +} + +.able-popup { + z-index: 9200; +} + +.able-tooltip { + z-index: 9000; +} + +/* + Popup Menus +*/ +.able-popup { + position: absolute; + margin: 0; + padding: 0; + border-color: #000; + border-width: 1px; + background-color: #000; + opacity: 0.95; + border-radius: 5px; + display: grid; + cursor: default; + outline: 1px solid #656565; +} + +ul.able-popup { + list-style-type: none; +} + +.able-popup li { + padding: 4px 16px; + margin: 0; + width: auto; + color: #fff; +} + +.able-popup li:first-of-type { + border-radius: 5px 5px 0 0; +} + +.able-popup li:last-of-type { + border-radius: 0 0 5px 5px; +} + +.able-popup li.able-focus { + background-color: #ccc; + color: #000; +} + +.able-popup-captions li[aria-checked="true"]::before { + content: "\2713" / ''; /* check mark; for heavier mark us 2714 */ + margin-right: 4px; +} + +/* Transcript */ +.able-transcript-area { + border-width: 1px; + border-style: solid; + z-index: 7000; + padding-bottom: 25px; /* keep content above drag icon */ + background-color: #fff; +} + +.able-transcript { + position: relative; /* must be positioned for scrolling */ + overflow-y: scroll; + padding-left: 1rem; + padding-right: 1rem; + background-color: #fff; + height: 350px; +} + +.able-transcript div { + margin: 1em 0; +} + +.able-transcript-heading { + font-size: 1.375rem; + font-weight: bold; + margin: .5rem 0; + padding: 0; +} + +.able-transcript-chapter-heading { + font-size: 1.125rem; + font-weight: bold; + margin: 0; + padding: 0; +} + +.able-transcript div.able-transcript-desc { + background-color: #fee; + border: 1px solid #336; + font-style: italic; + padding: .5rem; +} + +.able-transcript .able-unspoken { + font-weight: bold; +} + +.able-highlight, +.able-highlight span:hover, +.able-highlight span:focus, +.able-highlight span:active { + background-color: #000 !important; + color: #fff !important; + padding: 0.25em 0.10em; + border: none; + outline: none; +} + +.able-previous { + background: black !important; + font-style: italic; +} + +.able-transcript span:hover, +.able-transcript span:focus, +.able-transcript span:active { + background: #ffc; /* light yellow */ + color: #000; + border: none; + outline: none; + border-bottom: 1px solid #000; + cursor: pointer; +} + +.able-window-toolbar label { + display: inline; + margin-right: 10px; + color: #fff; +} + +.able-popup li:hover, +.able-window-toolbar input:focus, +.able-window-toolbar input:hover, +.able-window-toolbar select:focus, +.able-window-toolbar select:hover, +.able-controller input:focus, +.able-controller input:hover, +.able-controller div[role="button"]:focus, +.able-controller div[role="button"]:hover, +.able-seekbar-head:focus, +.able-seekbar-head:hover { + outline-style: solid; + outline-width: 2px; +} + +.able-controller div[role="button"]:focus, +div.able-modal-dialog input:focus, +div.able-modal-dialog button:focus, +.able-window-toolbar .able-button-handler-preferences:focus, +.able-window-toolbar input:focus, +.able-window-toolbar select:focus, +.able-controller input:focus, +.able-controller div[role="button"]:focus, +.able-seekbar-head:focus { + outline-color: #ffbb37; /* yellow */ +} + +/* Control hover colors. */ +.able-popup li:hover, +.able-controller div[role="button"]:hover, +div.able-modal-dialog input:hover, +div.able-modal-dialog button:hover, +.able-window-toolbar .able-button-handler-preferences:hover, +.able-window-toolbar input:hover, +.able-window-toolbar select:hover, +.able-controller input:hover, +.able-controller div[role="button"]:hover, +.able-seekbar-head:hover { + outline-color: #8ab839; /* green */ +} + +/* Playlist (both audio and video) */ +.able-playlist { + list-style-type: none; + margin: 0; + padding: 0; + display: grid; +} + +.able-playlist li { + background-color: #dfdfdf; /* default background color of each item in playlist */ + padding: 0; + margin: 0; + border: 1px solid #aaa; + width: auto; + border-bottom: none; + max-width: 100%; +} + +.able-playlist li:first-of-type { + border-radius: 5px 5px 0 0; +} + +.able-playlist li:last-of-type { + border-bottom: 2px solid #aaa; + border-radius: 0 0 5px 5px; +} + +.able-playlist li button { + border: none; + color: #000; + background-color: transparent; + font-size: .875rem; + width: 100%; + padding: 8px; + text-align: left; + display: flex; + align-items: center; + gap: 12px; +} + +.able-playlist li button:hover, +.able-playlist li button:focus, +.able-playlist li button:active { /* playlist items when they have mouse or keyboard focus */ + background-color: #feffb3; + color: #000; + text-decoration: none; + outline: none; +} + +.able-playlist li button img { + max-width: 100px; + max-height: 100px; + display: block; +} + +.able-playlist li.able-current { /* currently selected playlist item */ + background-color: #fff; + border-color: #230330; + outline: 2px solid #340449; + outline-offset: -2px; +} + +.able-playlist li.able-current button { + font-weight: bold; + text-decoration: none; + outline: none; +} + +.able-playlist li.able-current button:hover, +.able-playlist li.able-current button:focus, +.able-playlist li.able-current button:active { + color: #000; +} + +/* Search Styling */ +#able-search-term-echo { + font-weight: bold; + font-style: italic; +} + +.able-search-results ul li { + font-size: 1.1em; + margin-bottom: 1em; +} + +button.able-search-results-time { + font-size: 1em; + font-weight: bold; + cursor: pointer; +} + +button.able-search-results-time:hover, +button.able-search-results-time:focus, +button.able-search-results-time:active { + color: #fff; + background-color: #000; +} + +.able-search-results-text { + padding-left: 1em; +} + +.able-search-term { + background-color: #ffc; + font-weight: bold; +} + +#search-term { + font-weight: bold; + font-style: italic; +} + +/* Video Transcript Sorter (VTS) */ +#able-vts-instructions { + padding: 1em; + border: 1px solid #999; + width: 100%; + margin: 0 auto 1.5rem; + box-sizing: border-box; +} + +#able-vts fieldset { + margin: 0 auto 1.5rem; + border: none; +} + +#able-vts fieldset legend { + color: #000; + font-weight: bold; +} + +#able-vts fieldset div { + float: left; + display: flex; + gap: .5rem; + align-items: center; + padding-right: 1em; +} + +#able-vts thead tr { + background: #f0f0f0; +} + +#able-vts table, +#able-vts table th, +#able-vts table td { + border: 1px solid #323232; + border-collapse: collapse; + padding: 8px; + color: #323232; +} + +#able-vts table td[contenteditable="true"]:hover { + background: #fff; + color: #333; +} + +#able-vts table td[contenteditable="true"]:focus-within { + background: #fff; + color: #000; +} + +#able-vts table td button { + width: auto; + padding: 2px; + margin: 1px; + display: flex; + align-items: center; + float: left; + color: #323232; + background: #f6f6f6; + border-radius: 2px; +} + +#able-vts table td button svg { + width: 22px; + height: 22px; +} + +#able-vts table button:hover svg { + fill: #c00; +} + +tr.kind-chapters, +tr.kind-subtitles { + background-color: #fff; +} + +tr.kind-descriptions { + background-color: #fee; +} + +tr.kind-chapters { + background-color: #e6ffe6; +} + +.able-vts-dragging { + background-color: #ffc; +} + +div#able-vts-icon-credit { + font-size: .875rem; +} + +div#able-vts-alert { + display: none; + position: fixed; + top: 5px; + left: 5px; + border: 2px solid #666; + background-color: #ffc; + padding: 1em; + font-weight: bold; + z-index: 9400; +} + +button#able-vts-save { + font-size: 1em; + padding: 6px 12px; + border-radius: 4px; + margin-bottom: 1em; + font-weight: bold; +} + +button#able-vts-save:hover, +button#able-vts-save:focus { + color: #fff; + background-color: #060; +} + +.able-vts-output-instructions { + width: 720px; + max-width: 90%; +} + +#able-vts textarea { + height: 200px; + width: 720px; + max-width: 90%; +} + +/* Misc */ +.able-error { + display: block; + background: #ffc; + border: 2px solid #000; + color: #e00; + margin: 0.75em; + padding: 0.5em; +} + +.able-fallback { + display: block; + text-align: center; + border: 2px solid #333355; + background-color: #eee; + color: #000; + font-weight: bold; + font-size: 1.1em; + padding: 1em; + margin-bottom: 1em; + max-width: 500px; + width: 95%; +} + +.able-fallback div, +.able-fallback ul, +.able-fallback p { + text-align: left; +} + +.able-fallback li { + font-weight: normal; +} + +.able-fallback img { + width: 90%; + margin: 1em auto; + opacity: 0.3; +} + +.able-fallback img.able-poster { + position: relative; +} + +/* SVG Icons */ +.able-wrapper div[role="button"] svg, +.able-modal-dialog div[role="button"] svg, +.able-wrapper button svg, +.able-modal-dialog button svg { + display: block; + fill: currentColor; +} From 4d5add9424ac138248b31593351a51e8dff2eda8 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Mon, 21 Jul 2025 11:11:53 -0500 Subject: [PATCH 004/171] Add some additional handling for popups; remove able-player secondary background. --- styles/base.css | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/styles/base.css b/styles/base.css index 86456a5f..57254a75 100644 --- a/styles/base.css +++ b/styles/base.css @@ -84,7 +84,6 @@ .able-player { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - background-color: #262626; /* background color of player (appears on top & bottom) */ } /* Screen reader hidden text classes */ @@ -794,10 +793,12 @@ div.able-wrapper.fullscreen { /* Popup Menus */ -.able-popup { +.able ul.able-popup { position: absolute; margin: 0; padding: 0; + padding-inline-start: 0; + list-style-type: none; border-color: #000; border-width: 1px; background-color: #000; @@ -806,28 +807,25 @@ div.able-wrapper.fullscreen { display: grid; cursor: default; outline: 1px solid #656565; + width: auto; } -ul.able-popup { - list-style-type: none; -} - -.able-popup li { +.able ul.able-popup li { padding: 4px 16px; margin: 0; width: auto; color: #fff; } -.able-popup li:first-of-type { +.able ul.able-popup li:first-of-type { border-radius: 5px 5px 0 0; } -.able-popup li:last-of-type { +.able ul.able-popup li:last-of-type { border-radius: 0 0 5px 5px; } -.able-popup li.able-focus { +.able ul.able-popup li.able-focus { background-color: #ccc; color: #000; } From b8094ca7b1933d2af5edc2b1a2918f00c315020e Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Mon, 21 Jul 2025 18:15:05 -0500 Subject: [PATCH 005/171] Support default value for `kind` If the `kind` attribute is not set, the default value is `subtitles`. If omitted, set to `subtitles`. Also check tracks without the kind attribute when assessing whether a transcript is posible. Fixes #618 --- scripts/ableplayer-base.js | 2 +- scripts/track.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ableplayer-base.js b/scripts/ableplayer-base.js index 70f55ae8..66fb5515 100644 --- a/scripts/ableplayer-base.js +++ b/scripts/ableplayer-base.js @@ -283,7 +283,7 @@ var AblePlayerInstances = []; console.log('ERROR: Able Player transcript is missing required parts'); } } - else if ($(media).find('track[kind="captions"], track[kind="subtitles"]').length > 0) { + else if ($(media).find('track[kind="captions"], track[kind="subtitles"],track:not([kind])').length > 0) { // required tracks are present. COULD automatically generate a transcript if (this.transcriptDivLocation) { this.transcriptType = 'external'; diff --git a/scripts/track.js b/scripts/track.js index e562f57e..5831e8ce 100644 --- a/scripts/track.js +++ b/scripts/track.js @@ -30,7 +30,7 @@ } for (i = 0; i < tracks.length; i++) { track = tracks[i]; - kind = track.kind; + kind = ( track.kind ) ? track.kind : 'subtitles'; if (!track.src) { if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { From 6c4c0551651b3a580c5970c73d66a4ceb632c735 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Mon, 21 Jul 2025 18:26:10 -0500 Subject: [PATCH 006/171] Replace pipe.png with pipe character. Avoids having the extra HTTP call to load an otherwise unneeded img. Adds styling to decrease the importance and provide a bit more separation between control groups. Makes custom styling easier. Good change to have along with the new skin for 4.7. Fixes #550 --- button-icons/black/pipe.png | Bin 131 -> 0 bytes button-icons/white/pipe.png | Bin 134 -> 0 bytes scripts/buildplayer.js | 10 +++------- styles/base.css | 7 +++++++ 4 files changed, 10 insertions(+), 7 deletions(-) delete mode 100755 button-icons/black/pipe.png delete mode 100755 button-icons/white/pipe.png diff --git a/button-icons/black/pipe.png b/button-icons/black/pipe.png deleted file mode 100755 index 8733f8607756b187c27b686933c14d0fce38d88c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=m#2$kh=qT0iUOYr!_=qD8x<6v z_OysNJauHd#%$>Ll|{5i%EO?8Lra3!xyjKb;9!P?5RXz5m)qe92?By44yO%t7^L(Z cRzopr0E}}YH~;_u diff --git a/button-icons/white/pipe.png b/button-icons/white/pipe.png deleted file mode 100755 index 972190029d3fb076cb7e9b6e2bcc29bd2ba638a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=ucwP+h=u>#D;otF1O!|HZ!=E4 z#5i}pvGRYRn_hcwK44$|>4Q>|>g!2Tg@P9U%L|?', { 'tabindex': '-1', - 'aria-hidden': 'true' + 'aria-hidden': 'true', + 'class': 'able-pipe', }); if (this.iconType === 'font') { $pipe.addClass('icon-pipe'); } else { - $pipeImg = $('', { - src: this.rootPath + 'button-icons/' + this.iconColor + '/pipe.png', - alt: '', - role: 'presentation' - }); - $pipe.append($pipeImg); + $pipe.append('|'); } $controllerSpan.append($pipe); } diff --git a/styles/base.css b/styles/base.css index 57254a75..58f25414 100644 --- a/styles/base.css +++ b/styles/base.css @@ -69,6 +69,13 @@ padding: 4px 8px; } +.able-pipe { + position: relative; + top: -2px; + color: #a8a8a8; + margin: 0 6px; +} + .able .able-vidcap-container { left: 0; margin: 0; From 56642e3b81636e6de846de4d2308d4fd79ba62e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 07:03:51 +0000 Subject: [PATCH 007/171] Bump form-data from 4.0.2 to 4.0.4 Bumps [form-data](https://github.com/form-data/form-data) from 4.0.2 to 4.0.4. - [Release notes](https://github.com/form-data/form-data/releases) - [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v4.0.2...v4.0.4) --- updated-dependencies: - dependency-name: form-data dependency-version: 4.0.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7124b7c4..4799083a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3351,14 +3351,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { From f2a0cd99d62394452fe9f2954799f4fdef03fd50 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 14:36:11 -0500 Subject: [PATCH 008/171] Check that the target transcript div exists before defining as external. Fixes #652 --- scripts/ableplayer-base.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/ableplayer-base.js b/scripts/ableplayer-base.js index 66fb5515..ab60c148 100644 --- a/scripts/ableplayer-base.js +++ b/scripts/ableplayer-base.js @@ -257,10 +257,12 @@ var AblePlayerInstances = []; // There are three types of interactive transcripts. // In descending of order of precedence (in case there are conflicting tags), they are: // 1. "manual" - A manually coded external transcript (requires data-transcript-src) - // 2. "external" - Automatically generated, written to an external div (requires data-transcript-div) + // 2. "external" - Automatically generated, written to an external div (requires data-transcript-div & a valid target element) // 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button // If data-include-transcript="false", there is no "popup" transcript - if ($(media).data('transcript-div') !== undefined && $(media).data('transcript-div') !== "") { + if ($(media).data('transcript-div') !== undefined + && $(media).data('transcript-div') !== "" + && null !== document.getElementById( $(media).data('transcript-div') ) ) { this.transcriptDivLocation = $(media).data('transcript-div'); } else { @@ -273,6 +275,8 @@ var AblePlayerInstances = []; this.hideTranscriptButton = null; } + console.log( this ); + this.transcriptType = null; if ($(media).data('transcript-src') !== undefined) { this.transcriptSrc = $(media).data('transcript-src'); From 3ff9e927675bdb747a53dfe64b939427fc6c68f7 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 15:57:51 -0500 Subject: [PATCH 009/171] Remove debugging --- scripts/ableplayer-base.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/ableplayer-base.js b/scripts/ableplayer-base.js index ab60c148..8cb81659 100644 --- a/scripts/ableplayer-base.js +++ b/scripts/ableplayer-base.js @@ -275,8 +275,6 @@ var AblePlayerInstances = []; this.hideTranscriptButton = null; } - console.log( this ); - this.transcriptType = null; if ($(media).data('transcript-src') !== undefined) { this.transcriptSrc = $(media).data('transcript-src'); From 429068434210e25ab8e335bd1c3979a48c987aca Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 15:59:14 -0500 Subject: [PATCH 010/171] Remove the getYouTubeTimedTextUrl function The API no longer exists, and the Git history is sufficient documentation. --- scripts/youtube.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index c2728a53..fdd89c8c 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -305,22 +305,6 @@ return promise; }; - AblePlayer.prototype.getYouTubeTimedTextUrl = function (youTubeId, trackName, trackLang) { - - // return URL for retrieving WebVTT source via YouTube's timedtext API - // Note: This API seems to be undocumented, and could break anytime - // UPDATE: Google removed this API on November 10, 2021 - // This function is no longer called, but is preserved here for reference - var url = 'https://www.youtube.com/api/timedtext?fmt=vtt'; - url += '&v=' + youTubeId; - url += '&lang=' + trackLang; - // if track has a value in the name field, it's *required* in the URL - if (trackName !== '') { - url += '&name=' + trackName; - } - return url; - }; - AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) { // return a URL for retrieving a YouTube poster image From f7d7e4930fef2880da49d0c0a802a39be2fd6cbf Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 16:09:00 -0500 Subject: [PATCH 011/171] Add paths to get larger YouTube poster images. Not used, but make options. --- scripts/youtube.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index fdd89c8c..b4b10b83 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -308,7 +308,7 @@ AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) { // return a URL for retrieving a YouTube poster image - // supported values of width: 120, 320, 480, 640 + // supported values of width: 120, 320, 480, 640, 1280, 1920. var url = 'https://img.youtube.com/vi/' + youTubeId; if (width == '120') { @@ -317,7 +317,7 @@ } else if (width == '320') { // medium quality thumbnail, 320 x 180 - return url + '/hqdefault.jpg'; + return url + '/mqdefault.jpg'; } else if (width == '480') { // high quality thumbnail, 480 x 360 @@ -327,6 +327,14 @@ // standard definition poster image, 640 x 480 return url + '/sddefault.jpg'; } + else if (width == '1280') { + // standard definition poster image, 640 x 480 + return url + '/hq720.jpg'; + } + else if ( width == '1920' ) { + // standard definition poster image, 640 x 480 + return url + '/maxresdefault.jpg'; + } return false; }; From 5dc80d2031c8fa1e52e883bfc54bb81e0cf906ab Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 16:40:09 -0500 Subject: [PATCH 012/171] Just a spelling fix. --- scripts/youtube.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index b4b10b83..9d61dd1f 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -223,7 +223,7 @@ // no tracks were found, probably because the captions module hasn't loaded // play video briefly (required in order to load the captions module) - // and after the apiChange event is triggered, try again to retreive tracks + // and after the apiChange event is triggered, try again to retrieve tracks this.youTubePlayer.addEventListener('onApiChange',function(x) { // getDuration() also requires video to play briefly From 89dcefae45c40167f8e42e8ac740d6f588450758 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 18:11:43 -0500 Subject: [PATCH 013/171] Move duration check to the main event declaration on the YT object. This is mostly so that we're not mixing setting tracks with getting duration. --- scripts/youtube.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/youtube.js b/scripts/youtube.js index 9d61dd1f..ccfec820 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -168,6 +168,10 @@ } } }, + onApiChange: function() { + // getDuration() can be fetched during API change event. + thisObj.duration = thisObj.youTubePlayer.getDuration(); + }, onPlaybackQualityChange: function () { // do something }, From e60fa4dfe87aeef1fa64e0029da65c075577639c Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Wed, 23 Jul 2025 18:41:11 -0500 Subject: [PATCH 014/171] Correct spelling of iOS in comments. --- demos/video5.html | 2 +- scripts/browser.js | 4 ++-- scripts/buildplayer.js | 10 +++++----- scripts/sign.js | 4 ++-- scripts/youtube.js | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/demos/video5.html b/demos/video5.html index ce747182..191238a6 100644 --- a/demos/video5.html +++ b/demos/video5.html @@ -39,7 +39,7 @@

Video with sign language

An accompanying sign language video is provided for each <source> element using the data-sign-src attribute. This results in a sign language video being played in a pop-up window - in sync with the program video. Unfortunately, this does not currently work in IOS. + in sync with the program video. Unfortunately, this does not currently work in iOS.

diff --git a/scripts/browser.js b/scripts/browser.js index ed7229dc..d37c4c9e 100644 --- a/scripts/browser.js +++ b/scripts/browser.js @@ -2,7 +2,7 @@ AblePlayer.prototype.isIOS = function(version) { - // return true if this is IOS + // return true if this is iOS // if version is provided check for a particular version var userAgent, iOS; @@ -25,7 +25,7 @@ } } else { - // this is not IOS + // this is not iOS return false; } }; diff --git a/scripts/buildplayer.js b/scripts/buildplayer.js index 03100a3b..1ccb74e8 100644 --- a/scripts/buildplayer.js +++ b/scripts/buildplayer.js @@ -3,17 +3,17 @@ AblePlayer.prototype.injectPlayerCode = function() { // create and inject surrounding HTML structure - // If IOS: + // If iOS: // If video: - // IOS does not support any of the player's functionality + // iOS does not support any of the player's functionality // - everything plays in its own player // Therefore, AblePlayer is not loaded & all functionality is disabled - // (this all determined. If this is IOS && video, this function is never called) + // (this all determined. If this is iOS && video, this function is never called) // If audio: // HTML cannot be injected as a *parent* of the