[35445] | 1 | /**
|
---|
| 2 | * @file Provides an interface for playing transcription audio.
|
---|
| 3 | * @author Carl Stephens
|
---|
| 4 | * @module
|
---|
| 5 | */
|
---|
| 6 |
|
---|
| 7 | class LoadedAudio {
|
---|
| 8 | /**
|
---|
| 9 | * Initialises a new instance of the {@link LoadedAudio} class.
|
---|
| 10 | * @param {String | null} id The ID of the transcription for which this audio belongs to.
|
---|
| 11 | * @param {String | null} url The audio object URL.
|
---|
| 12 | */
|
---|
| 13 | constructor(id, url) {
|
---|
| 14 | /** @type {String | null} The ID of the transcription for which this audio belongs to. */
|
---|
| 15 | this.id = id;
|
---|
| 16 |
|
---|
| 17 | /** @type {String | null} The audio object URL. */
|
---|
| 18 | this.url = url;
|
---|
| 19 | }
|
---|
| 20 | }
|
---|
| 21 |
|
---|
| 22 | /**
|
---|
| 23 | * Polls an audio element's current time and updates the vuex store accordingly.
|
---|
| 24 | * @param {Audio} audioElement The audio element.
|
---|
| 25 | * @param {*} store The vuex store to update.
|
---|
| 26 | */
|
---|
| 27 | function pollAudioTime(audioElement, store) {
|
---|
[36872] | 28 | let lastTime = 0;
|
---|
[35445] | 29 |
|
---|
[35446] | 30 | (function poll() {
|
---|
[35445] | 31 | if (audioElement.currentTime !== lastTime) {
|
---|
| 32 | lastTime = audioElement.currentTime;
|
---|
[35450] | 33 | store.commit("playbackStateSetTime", { id: store.state.playbackState.id, time: lastTime });
|
---|
[35445] | 34 | }
|
---|
| 35 |
|
---|
[35502] | 36 | requestAnimationFrame(poll);
|
---|
[35445] | 37 | })();
|
---|
| 38 | }
|
---|
| 39 |
|
---|
| 40 | /**
|
---|
| 41 | * Loads an audio file.
|
---|
[35631] | 42 | * @param {HTMLAudioElement} player The audio player.
|
---|
[35445] | 43 | * @param {TranscriptionViewModel} transcription The name of the requested audio file.
|
---|
| 44 | * @param {LoadedAudio} current The currently loaded audio.
|
---|
| 45 | * @returns {LoadedAudio} If a new audio file was loaded, a new audio tracking object, else the current one.
|
---|
| 46 | */
|
---|
| 47 | function loadAudio(player, transcription, current) {
|
---|
| 48 | // TODO: Need to profile on some larger tracks; this may not be worth it? Better to cache a URL object instead?
|
---|
| 49 | if (current.url !== null) {
|
---|
| 50 | URL.revokeObjectURL(current.url);
|
---|
| 51 | }
|
---|
| 52 |
|
---|
| 53 | const urlObject = URL.createObjectURL(transcription.file);
|
---|
| 54 | player.src = urlObject;
|
---|
| 55 | player.load();
|
---|
| 56 |
|
---|
| 57 | return new LoadedAudio(transcription.id, urlObject);
|
---|
| 58 | }
|
---|
| 59 |
|
---|
| 60 | export default class AudioPlayback {
|
---|
| 61 | /**
|
---|
| 62 | * Initialises the {@link AudioPlayback} class.
|
---|
| 63 | * @param {*} store The vuex store to update.
|
---|
| 64 | */
|
---|
| 65 | static initialise(store) {
|
---|
| 66 | /** @type The vuex store. */
|
---|
| 67 | this.store = store;
|
---|
| 68 |
|
---|
| 69 | this.player = new Audio();
|
---|
| 70 | this.loadedAudio = new LoadedAudio(null, null);
|
---|
[35631] | 71 | this.requestedPlaybackTime = 0;
|
---|
[35445] | 72 |
|
---|
[35446] | 73 | pollAudioTime(this.player, store);
|
---|
| 74 |
|
---|
| 75 | this.player.addEventListener("ended", function() {
|
---|
| 76 | store.commit("playbackStateSetIsPlaying", false);
|
---|
| 77 | });
|
---|
[35445] | 78 | }
|
---|
| 79 |
|
---|
[35631] | 80 | static onCanPlayThrough() {
|
---|
| 81 | AudioPlayback.player.removeEventListener("canplaythrough", AudioPlayback.onCanPlayThrough);
|
---|
| 82 |
|
---|
| 83 | AudioPlayback.player.currentTime = AudioPlayback.requestedPlaybackTime;
|
---|
| 84 | AudioPlayback.store.commit("playbackStateSetLength", { id: AudioPlayback.loadedAudio.id, time: AudioPlayback.player.duration });
|
---|
| 85 | }
|
---|
| 86 |
|
---|
[35445] | 87 | /**
|
---|
[35631] | 88 | * Loads a transcription's audio file.
|
---|
| 89 | * @param {String} id The ID of the transcription to load the audio of.
|
---|
| 90 | * @param {Number} startTime The time into the audio to set the current playback time to. Leave negative to select the last value, or zero.
|
---|
[35445] | 91 | */
|
---|
[35631] | 92 | static async load(id, startTime = -1) {
|
---|
| 93 | this.player.addEventListener("canplaythrough", this.onCanPlayThrough);
|
---|
| 94 |
|
---|
[35450] | 95 | const playbackTimes = this.store.state.playbackState.playbackTimes;
|
---|
[35445] | 96 |
|
---|
[35631] | 97 | this.requestedPlaybackTime = startTime;
|
---|
[35450] | 98 | if (playbackTimes.has(id) && startTime < 0) {
|
---|
[35631] | 99 | this.requestedPlaybackTime = playbackTimes.get(id);
|
---|
[35445] | 100 | }
|
---|
| 101 |
|
---|
[35446] | 102 | if (this.loadedAudio.id !== id) {
|
---|
| 103 | this.loadedAudio = loadAudio(this.player, this.store.state.rawTranscriptions.get(id), this.loadedAudio);
|
---|
| 104 |
|
---|
| 105 | this.store.commit("playbackStateSetID", id);
|
---|
| 106 | }
|
---|
| 107 |
|
---|
[35631] | 108 | this.player.currentTime = AudioPlayback.requestedPlaybackTime;
|
---|
| 109 | this.store.commit("playbackStateSetLength", { id: id, time: this.player.duration });
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | /**
|
---|
| 113 | * Plays a transcription's audio file.
|
---|
| 114 | * @param {String} id The ID of the transcription to play audio for.
|
---|
| 115 | * @param {Number} startTime The time into the audio at which to start playing. Leave negative to resume playback if paused.
|
---|
| 116 | */
|
---|
| 117 | static async play(id, startTime = -1) {
|
---|
[35755] | 118 | // Start at the beginning if we've reached the end
|
---|
| 119 | if (this.player.duration <= this.player.currentTime && startTime < 0) {
|
---|
| 120 | await this.load(id, 0);
|
---|
| 121 | }
|
---|
| 122 | else {
|
---|
| 123 | await this.load(id, startTime);
|
---|
| 124 | }
|
---|
| 125 |
|
---|
[35445] | 126 | await this.player.play();
|
---|
[35446] | 127 | this.store.commit("playbackStateSetIsPlaying", true);
|
---|
[35445] | 128 | }
|
---|
| 129 |
|
---|
[35450] | 130 | /**
|
---|
[35451] | 131 | * Sets the current time of the audio player.
|
---|
| 132 | * @param {Number} time The time to scrub to.
|
---|
| 133 | * @param {Boolean} fromCurrent Indicates if the time is an offset.
|
---|
| 134 | */
|
---|
| 135 | static scrub(time, fromCurrent) {
|
---|
| 136 | const newTime = fromCurrent ? this.player.currentTime + time : time;
|
---|
| 137 | this.player.currentTime = newTime;
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | /**
|
---|
[35450] | 141 | * Pauses playback.
|
---|
| 142 | */
|
---|
[35445] | 143 | static pause() {
|
---|
| 144 | this.player.pause();
|
---|
[35446] | 145 | this.store.commit("playbackStateSetIsPlaying", false);
|
---|
[35445] | 146 | }
|
---|
[35802] | 147 |
|
---|
| 148 | /**
|
---|
| 149 | * Resumes playback.
|
---|
| 150 | * @param {Number} startTime The time into the audio at which to resume playback.
|
---|
| 151 | */
|
---|
| 152 | static resume(startTime = -1) {
|
---|
| 153 | this.play(this.loadedAudio.id, startTime);
|
---|
| 154 | }
|
---|
[35445] | 155 | }
|
---|