Demo: https://codepen.io/phillipgray/pen/bOmeed
The custom-read-aloud plugin is designed as a text and audio synchronization alternative to EPUB Media Overlays. It is inteded for use cases where automatic page-turning is not required.
EPUB Media Overlays 3.1 defines a set of standards for reading systems to provide \"enhanced accessibility for any user who has difficulty following the text of a traditional book\". Though a valuable accesibility feature for more traditional text-heavy ebooks and children\'s literature, the specification does not directly address second language learning. Further, vendor implementations vary and provide a less than adequate API for developers of interactive textbooks, who may want to put more control in the reader\'s hands and can do without the page-turn feature.
This is a purely front-end progressive enhancement approach. It executes after the page is loaded and creates event listeners on the audio element and text to be synced. These listeners add a CSS class to a corresponding markup delimited segment of text as the audio is playing.
To avoid potential conflicts in some reading systems, it should not be used with Media Overlays or within a book that may be utilizing that feature.
The plugin provides a JavaScript class `CustomReadAloud` on the global object. This can be instantiated in JS with the `new` keyword, passing in element selectors as arguments for the two HTML elements to sync. The first argument must be a selector for the element containing the paragraphs or spans of text to be highlighted while the audio is playing. The second argument must be a selector for the audio element. In order to sync text with audio, data-attributes (specifying the start time in seconds) must be manually added to each chunk of text. Upon instantiation, the plugin will map the chunks of text with their specified start times and add event listeners to each chunk and the audio element. The plugin will also listen for `timeupdate` events emitted from the audio object (about 4 times a second) during playback. If the audio element\'s currentTime approximately matches (within 500ms) any of the text chunk start times, a CSS class will be applied to matching text chunk. A rule for this CSS class can be added to the stylesheet to create a highlight effect. Each time the class is added to an element containing a chunk of text, it is removed from any other elements that may have been previously \"highlighted\".
This version is heavily polyfilled to support some reading systems (such as Adobe Digital Editions) which have not yet implemented standard ES5 (2009) JavaScript features. Polyfilling greatly increases the size of the code, which subsequently increases page load time. A non-polyfilled version can be provided, but has only been tested with the latest versions of iBooks on macOS 10.13.6 and iPad iOS 12.0.
<div id="readAloudText">
<p>
<span data-playhead="13.111">There is</span>
<span data-playhead="13.53444">no Frigate</span>
<span data-playhead="14">like</span>
<span data-playhead="14.52">a Book</span>
</p>
</div>
<audio
id="audioPlayer"
src="https://github.com/thephilgray/custom-readaloud-plugin/raw/master/./audio/05-there_is_no_frigate_dickinson_64kb.mp3"
controls
></audio>
<script src="https://github.com/thephilgray/custom-readaloud-plugin/raw/master/./script/custom-read-aloud-0.1.1.js" type="text/javascript"></script>
<script type="text/javascript">
// your code goes here
</script>
</body>
// your code goes here
var customReader = new CustomReadAloud("#readAloudText", "#audioPlayer");
.highlighted {
background: yellow;
}
If you are already using the `highlighted` classname in your CSS or prefer to change it to something else, pass in an options object on instantiation, including the `highlightClass` property with the value of your preferred classname.
var options = { highlightClass: "highlighted--red" };
var customReader = new CustomReadAloud(
"#readAloudText",
"#audioPlayer",
options
);
The default data-attribute name is `data-playhead`. You can customize the unique part of the name after the \"data-\" prefix.
var options = { dataAttribute: "start" };
var customReader = new CustomReadAloud(
"#readAloudText",
"#audioPlayer",
options
);
// now you can use data-start as the attribute, as in <span data-start="2">epub</span>
Disable to prevent user from touching or clicking text to begin playback.
Only play the current highlight and then stop. Only to be used with touchTextToPlay
; does not affect normal playback.
The start time for the audio clip.
The end time for the audio clip.
The initial playback rate for the audio element.
You can include multiple options in any order. The options object must be the third argument in the constructor call. All options have defaults, so the argument is optional.
var options = {
dataAttribute: "start",
highlightClass: "readAloudHighlight",
audioClipBegin: 3.5111,
audioClipEnd: 21.2,
touchTextToPlay: false,
playbackRate: 1.5
};
var customReader = new CustomReadAloud(
"#readAloudText",
"#audioPlayer",
options
);
Sets `currentTime` and `playbackRate` on the audio element and calls its `play` method.
To replace the default HTML5 audio play button:
<!-- 1. Remove the `controls` attribute from the `audio` tag. -->
<audio
id="audioPlayer"
src="https://github.com/thephilgray/custom-readaloud-plugin/raw/master/./audio/05-there_is_no_frigate_dickinson_64kb.mp3"
></audio>
<!-- 2. Create a button with an `onclick` attribute. Set its value to the customReader.play(), assuming you assigned the class instance to `var customReader`. -->
<button onclick="customReader.play()">Play</button>
Calls the audio element\'s `pause` method.
<button onclick="customReader.pause()">Pause</button>
Simulates `stop` functionality by calling the element\'s `pause` button and resetting its `currentTime` to 0. Also removes all highlights and resets `current`.
<button onclick="customReader.stop()">Stop</button>
The initial playback rate for the audio element.
Updates the audio element\'s playback rate.
NOTE: Currently incompatible with Adobe Digital Editions.
To create a custom playback rate control:
<!-- Create a new HTML input element; it should at the very least have a selector and a value that is always a number -->
<input
id="rate"
name="rate"
type="range"
min="0.5"
max="2"
step=".25"
value="1"
/>
// select the input and add an event listener that listens to the `change` event
// on change, pause the audio, call the `changePlaybackRate` method on the instance, passing in `event.target.value`, and then resume playing
var rateController = document.querySelector("#rate");
rateController.addEventListener("change",
function(e){
customReader.pause(); customReader.changePlaybackRate(e.target.value); customReader.play();
};
);
The audio player emits this custom event whenever playback state changes to play, pause, or end. The event object contains a payload, `event.detail.isPlaying`, which returns a boolean value for whether or not the audio is currently playing. This value is identical and can be used interchangeably with `CustomReadAloud.isPlaying`.
To subscribe to the event:
// add an event listener to the audio element used to instantiate the class
var customReader = new CustomReadAloud( '#readAloudText','#audioPlayer');
var audioEl = document.querySelector("#audioPlayer");
audioEl.addEventListener("playStateChange",
function(e) {
if(e.detail.isPlaying) {
// do something if audio is playing
};
else {
// do something else if audio is not playing
};
};
);
Dispatched whenver the highlighted element changes.