mirror of
https://github.com/google/blockly.git
synced 2025-12-16 06:10:12 +01:00
* refactor: Remove uses of AnyDuringMigration from flyout_vertical.ts. * refactor: Remove uses of AnyDuringMigration in flyout_horizontal.ts. * refactor: Remove uses of AnyDuringMigration from zoom_controls.ts. * refactor: Remove uses of AnyDuringMigration from comment.ts. * refactor: Remove uses of AnyDuringMigration from dialog.ts. * refactor: Remove uses of AnyDuringMigration from icon.ts. * refactor: Remove uses of AnyDuringMigration from scrollbar_pair.ts. * refactor: Remove uses of AnyDuringMigration from workspace_audio.ts. * refactor: Remove uses of AnyDuringMigration from workspace_drag_surface_svg.ts.
154 lines
4.4 KiB
TypeScript
154 lines
4.4 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Object in charge of loading, storing, and playing audio for a
|
|
* workspace.
|
|
*
|
|
* @class
|
|
*/
|
|
import * as goog from '../closure/goog/goog.js';
|
|
goog.declareModuleId('Blockly.WorkspaceAudio');
|
|
|
|
import * as userAgent from './utils/useragent.js';
|
|
import type {WorkspaceSvg} from './workspace_svg.js';
|
|
|
|
|
|
/**
|
|
* Prevent a sound from playing if another sound preceded it within this many
|
|
* milliseconds.
|
|
*/
|
|
const SOUND_LIMIT = 100;
|
|
|
|
/**
|
|
* Class for loading, storing, and playing audio for a workspace.
|
|
*
|
|
* @alias Blockly.WorkspaceAudio
|
|
*/
|
|
export class WorkspaceAudio {
|
|
/** Database of pre-loaded sounds. */
|
|
private sounds = new Map<string, HTMLAudioElement>();
|
|
|
|
/** Time that the last sound was played. */
|
|
private lastSound_: Date|null = null;
|
|
|
|
/**
|
|
* @param parentWorkspace The parent of the workspace this audio object
|
|
* belongs to, or null.
|
|
*/
|
|
constructor(private parentWorkspace: WorkspaceSvg) {}
|
|
|
|
/**
|
|
* Dispose of this audio manager.
|
|
*
|
|
* @internal
|
|
*/
|
|
dispose() {
|
|
this.sounds.clear();
|
|
}
|
|
|
|
/**
|
|
* Load an audio file. Cache it, ready for instantaneous playing.
|
|
*
|
|
* @param 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 name Name of sound.
|
|
*/
|
|
load(filenames: string[], name: string) {
|
|
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) {
|
|
this.sounds.set(name, sound);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Preload all the audio files so that they play quickly when asked for.
|
|
*
|
|
* @internal
|
|
*/
|
|
preload() {
|
|
for (const sound of this.sounds.values()) {
|
|
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(
|
|
// Play without user interaction was prevented.
|
|
function() {});
|
|
} 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 name Name of sound.
|
|
* @param opt_volume Volume of sound (0-1).
|
|
*/
|
|
play(name: string, opt_volume?: number) {
|
|
const sound = this.sounds.get(name);
|
|
if (sound) {
|
|
// Don't play one sound on top of another.
|
|
const now = new Date();
|
|
if (this.lastSound_ !== null &&
|
|
now.getTime() - this.lastSound_.getTime() < 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() as HTMLAudioElement;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|