Files
blockly/core/workspace_audio.js
Aaron Dodson 3851b14627 refactor: Migrate to named exports (#5623)
* refactor: Migrate to named exports

* fix: Sort requires

* fix: Remove duplicate deps
2021-10-20 15:53:23 -07:00

164 lines
4.7 KiB
JavaScript

/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Object in charge of loading, storing, and playing audio for a
* workspace.
*/
'use strict';
/**
* Object in charge of loading, storing, and playing audio for a
* workspace.
* @class
*/
goog.module('Blockly.WorkspaceAudio');
const internalConstants = goog.require('Blockly.internalConstants');
const userAgent = goog.require('Blockly.utils.userAgent');
/* eslint-disable-next-line no-unused-vars */
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
const {globalThis} = goog.require('Blockly.utils.global');
/**
* Class for loading, storing, and playing audio for a workspace.
* @param {WorkspaceSvg} parentWorkspace The parent of the workspace
* this audio object belongs to, or null.
* @constructor
* @alias Blockly.WorkspaceAudio
*/
const WorkspaceAudio = function(parentWorkspace) {
/**
* The parent of the workspace this object belongs to, or null. May be
* checked for sounds that this object can't find.
* @type {WorkspaceSvg}
* @private
*/
this.parentWorkspace_ = parentWorkspace;
/**
* Database of pre-loaded sounds.
* @private
*/
this.SOUNDS_ = Object.create(null);
};
/**
* Time that the last sound was played.
* @type {Date}
* @private
*/
WorkspaceAudio.prototype.lastSound_ = null;
/**
* Dispose of this audio manager.
* @package
*/
WorkspaceAudio.prototype.dispose = function() {
this.parentWorkspace_ = null;
this.SOUNDS_ = null;
};
/**
* Load an audio file. Cache it, ready for instantaneous playing.
* @param {!Array<string>} filenames List of file types in decreasing order of
* preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav']
* Filenames include path from Blockly's root. File extensions matter.
* @param {string} name Name of sound.
*/
WorkspaceAudio.prototype.load = function(filenames, name) {
if (!filenames.length) {
return;
}
let audioTest;
try {
audioTest = new globalThis['Audio']();
} catch (e) {
// No browser support for Audio.
// IE can throw an error even if the Audio object exists.
return;
}
let sound;
for (let i = 0; i < filenames.length; i++) {
const filename = filenames[i];
const ext = filename.match(/\.(\w+)$/);
if (ext && audioTest.canPlayType('audio/' + ext[1])) {
// Found an audio format we can play.
sound = new globalThis['Audio'](filename);
break;
}
}
if (sound && sound.play) {
this.SOUNDS_[name] = sound;
}
};
/**
* Preload all the audio files so that they play quickly when asked for.
* @package
*/
WorkspaceAudio.prototype.preload = function() {
for (const name in this.SOUNDS_) {
const sound = this.SOUNDS_[name];
sound.volume = 0.01;
const playPromise = sound.play();
// Edge does not return a promise, so we need to check.
if (playPromise !== undefined) {
// If we don't wait for the play request to complete before calling
// pause() we will get an exception: (DOMException: The play() request was
// interrupted) See more:
// https://developers.google.com/web/updates/2017/06/play-request-was-interrupted
playPromise.then(sound.pause).catch(function() {
// Play without user interaction was prevented.
});
} else {
sound.pause();
}
// iOS can only process one sound at a time. Trying to load more than one
// corrupts the earlier ones. Just load one and leave the others uncached.
if (userAgent.IPAD || userAgent.IPHONE) {
break;
}
}
};
/**
* Play a named sound at specified volume. If volume is not specified,
* use full volume (1).
* @param {string} name Name of sound.
* @param {number=} opt_volume Volume of sound (0-1).
*/
WorkspaceAudio.prototype.play = function(name, opt_volume) {
const sound = this.SOUNDS_[name];
if (sound) {
// Don't play one sound on top of another.
const now = new Date;
if (this.lastSound_ !== null &&
now - this.lastSound_ < internalConstants.SOUND_LIMIT) {
return;
}
this.lastSound_ = now;
let mySound;
if (userAgent.IPAD || userAgent.ANDROID) {
// Creating a new audio node causes lag in Android and iPad. Android
// refetches the file from the server, iPad uses a singleton audio
// node which must be deleted and recreated for each new audio tag.
mySound = sound;
} else {
mySound = sound.cloneNode();
}
mySound.volume = (opt_volume === undefined ? 1 : opt_volume);
mySound.play();
} else if (this.parentWorkspace_) {
// Maybe a workspace on a lower level knows about this sound.
this.parentWorkspace_.getAudioManager().play(name, opt_volume);
}
};
exports.WorkspaceAudio = WorkspaceAudio;