-
Michael "Z" Goddard authored
- Stopping sounds fades them out avoiding potential audio clipping
Michael "Z" Goddard authored- Stopping sounds fades them out avoiding potential audio clipping
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
sound-library.jsx 5.77 KiB
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import VM from 'scratch-vm';
import AudioEngine from 'scratch-audio';
import analytics from '../lib/analytics';
import LibraryComponent from '../components/library/library.jsx';
import soundIcon from '../components/asset-panel/icon--sound.svg';
import soundLibraryContent from '../lib/libraries/sounds.json';
import soundTags from '../lib/libraries/sound-tags';
const messages = defineMessages({
libraryTitle: {
defaultMessage: 'Choose a Sound',
description: 'Heading for the sound library',
id: 'gui.soundLibrary.chooseASound'
}
});
class SoundLibrary extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'handleItemSelected',
'handleItemMouseEnter',
'handleItemMouseLeave'
]);
/**
* AudioEngine that will decode and play sounds for us.
* @type {AudioEngine}
*/
this.audioEngine = null;
/**
* A promise for the sound queued to play as soon as it loads and
* decodes.
* @type {Promise<SoundPlayer>}
*/
this.playingSoundPromise = null;
}
componentDidMount () {
this.audioEngine = new AudioEngine();
this.playingSoundPromise = null;
}
componentWillUnmount () {
this.stopPlayingSound();
}
stopPlayingSound () {
// Playback is queued, playing, or has played recently and finished
// normally.
if (this.playingSoundPromise !== null) {
// Queued playback began playing before this method.
if (this.playingSoundPromise.isPlaying) {
// Fetch the player from the promise and stop playback soon.
this.playingSoundPromise.then(soundPlayer => {
soundPlayer.stop();
});
} else {
// Fetch the player from the promise and stop immediately. Since
// the sound is not playing yet, this callback will be called
// immediately after the sound starts playback. Stopping it
// immediately will have the effect of no sound being played.
this.playingSoundPromise.then(soundPlayer => {
soundPlayer.stopImmediately();
});
}
// No further work should be performed on this promise and its
// soundPlayer.
this.playingSoundPromise = null;
}
}
handleItemMouseEnter (soundItem) {
const md5ext = soundItem._md5;
const idParts = md5ext.split('.');
const md5 = idParts[0];
const vm = this.props.vm;
// In case enter is called twice without a corresponding leave
// inbetween, stop the last playback before queueing a new sound.
this.stopPlayingSound();
// Save the promise so code to stop the sound may queue the stop
// instruction after the play instruction.
this.playingSoundPromise = vm.runtime.storage.load(vm.runtime.storage.AssetType.Sound, md5)
.then(soundAsset => {
const sound = {
md5: md5ext,
name: soundItem.name,
format: soundItem.format,
data: soundAsset.data
};
return this.audioEngine.decodeSoundPlayer(sound);
})
.then(soundPlayer => {
soundPlayer.connect(this.audioEngine);
// Play the sound. Playing the sound will always come before a
// paired stop if the sound must stop early.
soundPlayer.play();
// Set that the sound is playing. This affects the type of stop
// instruction given if the sound must stop early.
if (this.playingSoundPromise !== null) {
this.playingSoundPromise.isPlaying = true;
}
return soundPlayer;
});
}
handleItemMouseLeave () {
this.stopPlayingSound();
}
handleItemSelected (soundItem) {
const vmSound = {
format: soundItem.format,
md5: soundItem._md5,
rate: soundItem.rate,
sampleCount: soundItem.sampleCount,
name: soundItem.name
};
this.props.vm.addSound(vmSound).then(() => {
this.props.onNewSound();
});
analytics.event({
category: 'library',
action: 'Select Sound',
label: soundItem.name
});
}
render () {
// @todo need to use this hack to avoid library using md5 for image
const soundLibraryThumbnailData = soundLibraryContent.map(sound => {
const {
md5,
...otherData
} = sound;
return {
_md5: md5,
rawURL: soundIcon,
...otherData
};
});
return (
<LibraryComponent
data={soundLibraryThumbnailData}
id="soundLibrary"
tags={soundTags}
title={this.props.intl.formatMessage(messages.libraryTitle)}
onItemMouseEnter={this.handleItemMouseEnter}
onItemMouseLeave={this.handleItemMouseLeave}
onItemSelected={this.handleItemSelected}
onRequestClose={this.props.onRequestClose}
/>
);
}
}
SoundLibrary.propTypes = {
intl: intlShape.isRequired,
onNewSound: PropTypes.func.isRequired,
onRequestClose: PropTypes.func,
vm: PropTypes.instanceOf(VM).isRequired
};
export default injectIntl(SoundLibrary);