mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge pull request #1210 from thisandagain/feature/polly
Add initial working version of the Amazon Polly extension
This commit is contained in:
commit
c2645f3da5
2 changed files with 154 additions and 0 deletions
|
@ -11,6 +11,7 @@ const Scratch3PenBlocks = require('../extensions/scratch3_pen');
|
|||
const Scratch3WeDo2Blocks = require('../extensions/scratch3_wedo2');
|
||||
const Scratch3MusicBlocks = require('../extensions/scratch3_music');
|
||||
const Scratch3MicroBitBlocks = require('../extensions/scratch3_microbit');
|
||||
const Scratch3SpeakBlocks = require('../extensions/scratch3_speak');
|
||||
const Scratch3TranslateBlocks = require('../extensions/scratch3_translate');
|
||||
const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing');
|
||||
const Scratch3SpeechBlocks = require('../extensions/scratch3_speech');
|
||||
|
@ -20,6 +21,7 @@ const builtinExtensions = {
|
|||
wedo2: Scratch3WeDo2Blocks,
|
||||
music: Scratch3MusicBlocks,
|
||||
microbit: Scratch3MicroBitBlocks,
|
||||
speak: Scratch3SpeakBlocks,
|
||||
translate: Scratch3TranslateBlocks,
|
||||
videoSensing: Scratch3VideoSensingBlocks,
|
||||
speech: Scratch3SpeechBlocks
|
||||
|
|
152
src/extensions/scratch3_speak/index.js
Normal file
152
src/extensions/scratch3_speak/index.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
const formatMessage = require('format-message');
|
||||
const nets = require('nets');
|
||||
|
||||
const ArgumentType = require('../../extension-support/argument-type');
|
||||
const BlockType = require('../../extension-support/block-type');
|
||||
const Cast = require('../../util/cast');
|
||||
const log = require('../../util/log');
|
||||
|
||||
/**
|
||||
* The url of the synthesis server.
|
||||
* @type {string}
|
||||
*/
|
||||
const SERVER_HOST = 'https://synthesis-service.scratch.mit.edu';
|
||||
|
||||
/**
|
||||
* How long to wait in ms before timing out requests to synthesis server.
|
||||
* @type {int}
|
||||
*/
|
||||
const SERVER_TIMEOUT = 10000; // 10 seconds (chosen arbitrarily).
|
||||
|
||||
/**
|
||||
* Class for the synthesis block in Scratch 3.0.
|
||||
* @constructor
|
||||
*/
|
||||
class Scratch3SpeakBlocks {
|
||||
constructor () {
|
||||
/**
|
||||
* Locale code of the viewer
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this._language = this.getViewerLanguageCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* The key to load & store a target's synthesis state.
|
||||
* @return {string} The key.
|
||||
*/
|
||||
static get STATE_KEY () {
|
||||
return 'Scratch.speak';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} metadata for this extension and its blocks.
|
||||
*/
|
||||
getInfo () {
|
||||
return {
|
||||
id: 'speak',
|
||||
name: 'Amazon Polly',
|
||||
menuIconURI: '', // @todo Add the final icons.
|
||||
blockIconURI: '',
|
||||
blocks: [
|
||||
{
|
||||
opcode: 'speakAndWait',
|
||||
text: formatMessage({
|
||||
id: 'speak.speakAndWaitBlock',
|
||||
default: 'speak [WORDS]',
|
||||
description: 'speak some words'
|
||||
}),
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
WORDS: {
|
||||
type: ArgumentType.STRING,
|
||||
defaultValue: formatMessage({
|
||||
id: 'speak.defaultTextToSpeak',
|
||||
default: 'hello',
|
||||
description: 'hello: the default text to speak'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
menus: {
|
||||
voices: this.supportedVoices
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the viewer's language code.
|
||||
* @return {string} the language code.
|
||||
*/
|
||||
getViewerLanguageCode () {
|
||||
// @todo This should be the language code of the project *creator*
|
||||
// rather than the project viewer.
|
||||
return navigator.language || navigator.userLanguage || 'en-US';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the supported voices for the extension.
|
||||
* @return {Array} Supported voices
|
||||
*/
|
||||
supportedVoices () {
|
||||
return [
|
||||
'Quinn',
|
||||
'Max',
|
||||
'Squeak',
|
||||
'Monster',
|
||||
'Puppy'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the provided text into a sound file and then play the file.
|
||||
* @param {object} args Block arguments
|
||||
* @return {Promise} A promise that resolves after playing the sound
|
||||
*/
|
||||
speakAndWait (args) {
|
||||
// Cast input to string
|
||||
args.WORDS = Cast.toString(args.WORDS);
|
||||
|
||||
// Build up URL
|
||||
let path = `${SERVER_HOST}/synth`;
|
||||
path += `?locale=${this._language}`;
|
||||
path += `&gender=male`; // @todo
|
||||
path += `&text=${encodeURI(args.WORDS)}`;
|
||||
|
||||
// Perform HTTP request to get audio file
|
||||
return new Promise(resolve => {
|
||||
nets({
|
||||
url: path,
|
||||
timeout: SERVER_TIMEOUT
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
log.warn(err);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
log.warn(res.statusCode);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// Play the sound
|
||||
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const source = ctx.createBufferSource();
|
||||
ctx.decodeAudioData(body.buffer, buffer => {
|
||||
source.buffer = buffer;
|
||||
source.connect(ctx.destination);
|
||||
source.loop = false;
|
||||
}, e => {
|
||||
log.warn(e.err);
|
||||
});
|
||||
source.addEventListener('ended', () => {
|
||||
resolve();
|
||||
});
|
||||
source.start(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
module.exports = Scratch3SpeakBlocks;
|
Loading…
Reference in a new issue