diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..71299c96
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,42 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**Version tested**
+Which version of AblePlayer are you reporting against?
+ - Main (the current release)
+ - Develop (the next release)
+ - A specific branch other than main or develop.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/docs_existing.md b/.github/ISSUE_TEMPLATE/docs_existing.md
new file mode 100644
index 00000000..f8f9f08b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/docs_existing.md
@@ -0,0 +1,26 @@
+---
+name: Documentation (Existing)
+about: Edits to existing documentation needed
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**URL**
+page URL
+
+**Title**
+Title of the existing documentation
+
+**What Needs Editing?**
+Describe the steps using:
+1.
+2.
+3.
+
+**Screenshots**
+If applicable, add screenshots for the above steps
+
+**Additional context**
+Add any other context.
diff --git a/.github/ISSUE_TEMPLATE/docs_new.md b/.github/ISSUE_TEMPLATE/docs_new.md
new file mode 100644
index 00000000..e0b5fce6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/docs_new.md
@@ -0,0 +1,26 @@
+---
+name: Documentation (New)
+about: New documentation needed
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Title**
+Suggested title of the new documentation
+
+**Description**
+Describe what the new page is about
+
+**Instruction Steps**
+Describe the steps using:
+1.
+2.
+3.
+
+**Screenshots**
+If applicable, add screenshots for the above steps
+
+**Additional context**
+Add any other context.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..bbcbbe7d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..628cfc07
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,26 @@
+
+
+## Description
+
+
+
+## How Has This Been Tested?
+
+
+
+
+## Screenshots (jpeg or gifs if applicable):
+
+## Types of changes
+
+
+
+
+
+## Checklist:
+- [ ] My code is tested.
+- [ ] My code has proper inline documentation.
diff --git a/.gitignore b/.gitignore
index 4122dad7..33ea1d0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,9 @@
.DS_Store
test.html
-thirdparty/jwplayer.*
-thirdparty/jquery-*.js
node_modules/
npm-debug.log
translations/???.js
.idea
.htaccess
docs
-jsdoc.json
\ No newline at end of file
+jsdoc.json
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 00000000..cdd6f80d
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,13 @@
+.DS_Store
+test.html
+node_modules/
+npm-debug.log
+translations/???.js
+.idea
+.htaccess
+docs
+jsdoc.json
+.git
+.github
+demos
+media
diff --git a/Gruntfile.cjs b/Gruntfile.cjs
new file mode 100644
index 00000000..69bdfca2
--- /dev/null
+++ b/Gruntfile.cjs
@@ -0,0 +1,59 @@
+module.exports = function (grunt) {
+ grunt.loadNpmTasks("grunt-contrib-copy");
+ grunt.loadNpmTasks("grunt-contrib-cssmin");
+ grunt.loadNpmTasks("grunt-contrib-clean");
+ grunt.loadNpmTasks('grunt-run');
+ grunt.loadNpmTasks('grunt-eslint');
+
+ grunt.initConfig({
+ pkg: grunt.file.readJSON("package.json"),
+ cssmin: {
+ min: {
+ src: ["styles/ableplayer.css"],
+ dest: "build/<%= pkg.name %>.min.css",
+ },
+ options: {
+ // Add a banner with the package name and version
+ // (no date, otherwise a new build is different even if the code didn't change!)
+ // (oddly, here we don't need a '\n' at the end!)
+ banner: "/*! <%= pkg.name %> V<%= pkg.version %> */",
+ },
+ },
+ run: {
+ rollup: {
+ cmd: 'node',
+ args: ['node_modules/rollup/dist/bin/rollup', '-c'],
+ },
+ jest: {
+ cmd: 'node',
+ args: ['node_modules/jest/bin/jest.js', '--colors']
+ },
+ types: {
+ cmd: 'node',
+ args: ['node_modules/typescript/bin/tsc', '-p', 'tsconfig.json']
+ },
+ },
+ copy: {
+ dompurify: {
+ files: {
+ 'build/separate-dompurify/purify.min.js': ['node_modules/dompurify/dist/purify.min.js'],
+ }
+ }
+ },
+ eslint: {
+ target: ['scripts/*.js'],
+ },
+ clean: {
+ build: ["build"],
+ },
+ });
+
+ grunt.registerTask("default", [
+ "run:rollup",
+ "run:types",
+ "copy:dompurify",
+ "cssmin",
+ ]);
+ grunt.registerTask("test", ["eslint"]);
+ grunt.registerTask("jest", ["run:rollup", "run:jest"]);
+};
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 44fb374a..00000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,172 +0,0 @@
-module.exports = function (grunt) {
- grunt.loadNpmTasks("grunt-contrib-concat");
- grunt.loadNpmTasks("grunt-contrib-copy");
- grunt.loadNpmTasks("grunt-contrib-cssmin");
- grunt.loadNpmTasks("grunt-contrib-clean");
- grunt.loadNpmTasks("grunt-remove-logging");
- grunt.loadNpmTasks("grunt-contrib-jshint");
- grunt.loadNpmTasks("grunt-terser");
-
- grunt.initConfig({
- pkg: grunt.file.readJSON("package.json"),
- concat: {
- options: {
- banner: "/*! <%= pkg.name %> V<%= pkg.version %> with DOMPurify included */\n",
- process: function(src, filepath) {
- // Remove the source map reference line only from the dompurify file
- if (filepath.includes('dompurify')) {
- return src.replace(/\/\/# sourceMappingURL=.*\.map/g, '');
- }
- return src;
- }
- },
- build: {
- src: [
- "node_modules/dompurify/dist/purify.js",
- "scripts/ableplayer-base.js",
- "scripts/initialize.js",
- "scripts/preference.js",
- "scripts/webvtt.js",
- "scripts/buildplayer.js",
- "scripts/validate.js",
- "scripts/track.js",
- "scripts/youtube.js",
- "scripts/slider.js",
- "scripts/volume.js",
- "scripts/dialog.js",
- "scripts/misc.js",
- "scripts/description.js",
- "scripts/browser.js",
- "scripts/control.js",
- "scripts/caption.js",
- "scripts/chapters.js",
- "scripts/metadata.js",
- "scripts/transcript.js",
- "scripts/search.js",
- "scripts/event.js",
- "scripts/dragdrop.js",
- "scripts/sign.js",
- "scripts/langs.js",
- "scripts/translation.js",
- "scripts/vts.js",
- "scripts/vimeo.js",
- ],
- dest: "build/<%= pkg.name %>.js",
- },
- build_separate_dompurify: {
- options: {
- banner: "/*! <%= pkg.name %> V<%= pkg.version %> - In this file, DOMPurify is not bundled in with AblePlayer, but is a required dependency that can be added to the project via a local copy or a CDN */\n",
- },
- src: [
- "scripts/ableplayer-base.js",
- "scripts/initialize.js",
- "scripts/preference.js",
- "scripts/webvtt.js",
- "scripts/buildplayer.js",
- "scripts/validate.js",
- "scripts/track.js",
- "scripts/youtube.js",
- "scripts/slider.js",
- "scripts/volume.js",
- "scripts/dialog.js",
- "scripts/misc.js",
- "scripts/description.js",
- "scripts/browser.js",
- "scripts/control.js",
- "scripts/caption.js",
- "scripts/chapters.js",
- "scripts/metadata.js",
- "scripts/transcript.js",
- "scripts/search.js",
- "scripts/event.js",
- "scripts/dragdrop.js",
- "scripts/sign.js",
- "scripts/langs.js",
- "scripts/translation.js",
- "scripts/vts.js",
- "scripts/vimeo.js",
- ],
- dest: "build/separate-dompurify/<%= pkg.name %>.js",
- },
- },
- removelogging: {
- dist: {
- src: ["build/<%= pkg.name %>.js"],
- dest: "build/<%= pkg.name %>.dist.js",
- },
- dist_separate_dompurify: {
- src: ["build/separate-dompurify/<%= pkg.name %>.js"],
- dest: "build/separate-dompurify/<%= pkg.name %>.dist.js",
- },
- options: {
- // Remove all console output (see https://www.npmjs.com/package/grunt-remove-logging)
- },
- },
- terser: {
- min: {
- files: {
- "build/<%= pkg.name %>.min.js": ["build/<%= pkg.name %>.dist.js"],
- },
- options: {
- ecma: 2015, // Specify ECMAScript version to support ES6+
- keep_fnames: true,
- output: {
- comments: /^!/,
- },
- },
- },
- min_separate_dompurify: {
- files: {
- "build/separate-dompurify/<%= pkg.name %>.min.js": ["build/separate-dompurify/<%= pkg.name %>.dist.js"],
- "build/separate-dompurify/purify.min.js": ["node_modules/dompurify/dist/purify.js"],
- },
- options: {
- ecma: 2015, // Specify ECMAScript version to support ES6+
- keep_fnames: true,
- output: {
- comments: /^!/,
- },
- },
- },
- },
- cssmin: {
- min: {
- src: ["styles/ableplayer.css"],
- dest: "build/<%= pkg.name %>.min.css",
- },
- options: {
- // Add a banner with the package name and version
- // (no date, otherwise a new build is different even if the code didn't change!)
- // (oddly, here we don't need a '\n' at the end!)
- banner: "/*! <%= pkg.name %> V<%= pkg.version %> */",
- },
- },
- jshint: {
- files: ["Gruntfile.js", "scripts/**/*.js"],
- options: {
- // options here to override JSHint defaults
- globals: {
- browser: true,
- jquery: true,
- devel: true,
- },
- },
- },
- clean: {
- build: ["build"],
- },
- });
-
- grunt.registerTask("default", [
- "concat:build",
- "removelogging:dist",
- "terser:min",
- "cssmin",
- ]);
- grunt.registerTask("build_separate_dompurify", [
- "concat:build_separate_dompurify",
- "removelogging:dist_separate_dompurify",
- "terser:min_separate_dompurify",
- ]);
- grunt.registerTask("test", ["jshint"]);
-};
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 32a7b851..916a0a8b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,9 +2,9 @@ The MIT License (MIT)
Copyright (c) 2014-2025 The Able Player Contributors
-Able Player development is supported in part by The AccessComputing project,
-with financial support from the National Science Foundation
-(grant #CNS-0540615, CNS-0837508, and CNS-1042260);
+Able Player development was supported in part by The AccessComputing project until 2025,
+with financial support from the National Science Foundation
+(grant #CNS-0540615, CNS-0837508, and CNS-1042260);
and by the Committee on Institutional Cooperation (CIC).
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/README.md b/README.md
index a2b80b21..3bce92a3 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
- [Feature List](#feature-list)
- [Supported Languages](#supported-languages)
- [Compatibility](#compatibility)
+- [Content Security Policies](#content-security-policies)
- [Dependencies](#dependencies)
- [Fallback](#fallback)
- [Setup](#setup)
@@ -25,9 +26,10 @@
- Features high contrast, scalable controls that remain visible in Windows High Contrast mode, plus an easy-to-see focus indicator so keyboard users can easily tell which control currently has focus.
- Supports closed captions and subtitles in Web Video Timed Text (WebVTT) format, the standard format recommended by the HTML5 specification.
- Supports chapters, also using WebVTT. Chapters are specific landing points in the video, allowing video content to have structure and be more easily navigated.
-- Supports text-based audio description, also using WebVTT. At designated times, the description text is read aloud by browsers, or by screen readers for browsers that don't support the Web Speech API. Users can optionally set their player to pause when audio description starts in order to avoid conflicts between the description and program audio.
+- Supports text-based audio description, also using WebVTT. At designated times, the description text is read aloud by browsers, or by screen readers for browsers that don't support the Web Speech API. Users can optionally set their player to pause when audio description starts to avoid conflicts between the description and program audio.
+- Supports spoken captions, as described in the "Spoken subtitles" requirement to EN 301 549. If enabled by a user, caption timing will be estimated and speed is dynamically adjusted to fit the available time. User can select a default speed, pitch, and volume for captions.
- Supports audio description as a separate video. When two videos are available (one with description and one without), both can be delivered together using the same player and users can toggle between the versions.
-- Supports adjustable playback rate. Users who need to slow down the video in order to better process and understand its content can do so; and users who need to speed up the video in order to maintain better focus can do so.
+- Supports adjustable playback rate. Users who need to slow down the video to better process and understand its content can do so; and users who need to speed up the video to maintain better focus can do so.
- Includes an interactive transcript feature, built from the WebVTT chapter, caption and description files as the page is loaded. Users can click anywhere in the transcript to start playing the video (or audio) at that point. Keyboard users can also choose to keyboard-enable the transcript, so they can tab through its content one caption at a time and press enter to play the media at the desired point.
- Features automatic text highlighting within the transcript as the media plays. This feature is enabled by default but can be turned off if users find it distracting.
- Supports YouTube and Vimeo videos.
@@ -84,7 +86,34 @@ During development, *Able Player* is routinely tested with the latest versions o
- Chrome
- Firefox
-With the release of version 4.4, we are no longer actively supporting Internet Explorer.
+Since the release of version 4.4, we are no longer supporting Internet Explorer.
+
+## Content Security Policies
+
+AblePlayer, by default, only references local sources with no inline scripts. However, it is possible to extend it using external scripting, and support for YouTube and Vimeo requires external resources.
+
+These content policies only cover the default requirements for Able Player; the environment you're using it in may have additional requirements.
+
+The Able Players demos get jQuery and jsCookie from content delivery networks. If you use those external sources, you will also need to use `script-src 'self' https://cdn.jsdelivr.net https://code.jquery.com`, or references to whatever external source you are using.
+
+### Using only local sources
+
+Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';
+
+### Using Vimeo
+
+Content-Security-Policy: default-src 'self' https://vimeo.com; script-src 'self' https://player.vimeo.com; style-src 'self'; frame-src 'self' https://player.vimeo.com;
+
+### Using YouTube
+
+The YouTube security policy uses both the 'nocookie' and the default YouTube domains. You do not need to allow both, depending on your usage.
+
+Content-Security-Policy: default-src 'self' https://img.youtube.com; script-src 'self' https://youtube.com https://nocookie.youtube.com; style-src 'self'; frame-src 'self' https://nocookie.youtube.com https://www.youtube.com;
+
+### Using YouTube & Vimeo
+
+Content-Security-Policy: default-src 'self' https://img.youtube.com https://vimeo.com; script-src 'self' https://player.vimeo.com https://youtube.com https://nocookie.youtube.com; style-src 'self'; frame-src 'self' https://www.youtube.com https://player.vimeo.com;
+
## Dependencies
@@ -92,20 +121,22 @@ With the release of version 4.4, we are no longer actively supporting Internet E
- *Able Player* uses [jQuery][]. Version 3.5.0 or higher is recommended.
The example code below uses Google’s hosted libraries; no download required.
-- *Able Player* uses [js-cookie][] to store and retrieve user
- preferences in cookies. The example code below uses CDN’s hosted libraries;
- no download required. Prior to version 2.3, Able Player used [jquery.cookie][]
- for this same purpose.
+- *Able Player* optionally can use [js-cookie][] to store and retrieve user
+ preferences in cookies. By default, preferences are stored in localStorage.
+ The example code below uses CDN’s hosted libraries; no download required.
+ All Able Player cookies are functional cookies.
- *AblePlayer*, as of 4.5.1, requires the use of the DOMPurify sanitizing library.
- The default files in the root of the `/build` directory have DOMPurify bundled in.
- Alternatively, the `build/separate-dompurify` directory houses copies of the AblePlayer files with AblePlayer code only and a stand-alone copy of the current version of DOMPurify that the project is currently using. These files are available for those who want to load DOMPurify via a separate file or want to use a CDN hosted version.
+- As of 5.0.0, *Able Player* includes an ES module bundle. This bundle `import`s jQuery and doesn't need it on `window`.
-To install Able Player, copy the following files from the Able Player repo into a folder on your web server:
+## Installation
+
+### `
+
+or
+
+
+
@@ -149,6 +206,38 @@ Copy and paste the following code into your web page. This code applies to all u
```
+#### ES module bundling with Vite or similar
+
+Include Able Player's CSS in your bundle. For example:
+
+```js
+import 'ableplayer/styles/ableplayer.css';
+```
+
+Then import the Able Player constructor into your application code.
+
+```js
+import AblePlayer from 'ableplayer';
+```
+
+#### RequireJS
+
+Include the CSS as in the `