Skip to content

Commit

Permalink
Merge pull request #20026 from code-dot-org/dtl_candidate_42348a9b
Browse files Browse the repository at this point in the history
  • Loading branch information
deploy-code-org committed Jan 15, 2018
2 parents 607c75f + 42348a9 commit 9e6330f
Show file tree
Hide file tree
Showing 331 changed files with 2,719 additions and 949 deletions.
20 changes: 12 additions & 8 deletions .circleci/config.yml
Expand Up @@ -23,17 +23,26 @@ jobs:
resource_class: xlarge

steps:
- run:
name: Create source cache key
command: "date +%D > /home/circleci/today.date"

# cache source
- restore_cache:
key: code-dot-org-source-v6-{{ .Branch }}-{{ .Revision }}
keys:
- code-dot-org-source-v6-{{ .Branch }}-
- code-dot-org-source-v6-
- code-dot-org-source-v7-{{ checksum "/home/circleci/today.date" }}
- code-dot-org-source-v7-

- checkout
- run:
name: merge staging
command: bin/circle/merge-base

- save_cache:
key: code-dot-org-source-v7-{{ checksum "/home/circleci/today.date" }}
paths:
- ".git"

- run:
name: setup tests/artifacts dirs
command: |
Expand All @@ -48,11 +57,6 @@ jobs:
sudo apt-get install -y libicu-dev enscript moreutils pdftk libmysqlclient-dev libsqlite3-dev
sudo mv /usr/bin/gnu_parallel /usr/bin/parallel
- save_cache:
key: code-dot-org-source-v6-{{ .Branch }}-{{ .Revision }}
paths:
- ".git"

# restore gems cache
- restore_cache:
key: code-dot-org-bundle-v6-{{ checksum "Gemfile.lock" }}
Expand Down
2 changes: 1 addition & 1 deletion apps/package.json
Expand Up @@ -45,7 +45,7 @@
"@code-dot-org/blockly": "1.7.0",
"@code-dot-org/bramble": "0.1.25",
"@code-dot-org/craft": "github:code-dot-org/craft#ea9da01f6512bcd2c8ea562d6a4ff4d2c0549e4f",
"@code-dot-org/johnny-five": "0.11.1-cdo.1",
"@code-dot-org/johnny-five": "0.11.1-cdo.2",
"@code-dot-org/js-interpreter-tyrant": "0.2.2",
"@code-dot-org/js-numbers": "0.1.0-cdo.0",
"@code-dot-org/p5.play": "1.1.9-cdo",
Expand Down
308 changes: 308 additions & 0 deletions apps/src/Sound.js
@@ -0,0 +1,308 @@
function isMobile() {
return ('ontouchstart' in document.documentElement);
}

function isIE9() {
/** @type {number} */
var version = -1;

if (/MSIE\s([\d.]+)/.test(navigator.userAgent)) {
version = parseInt(RegExp.$1);
}

return version === 9;
}

/**
* Initialize an individual sound
* @param config available sound files for this audio
* @param audioContext context this sound can be played on, or null if none
* @constructor
*/
export default function Sound(config, audioContext) {
this.config = config;
this.audioContext = audioContext;
this.audioElement = null; // if HTML5 Audio
this.reusableBuffer = null; // if Web Audio
this.playableBuffer = null; // if Web Audio

/**
* @private {boolean} Whether the sound is currently playing - sadly, neither
* audio system tracks this for us particularly well so we have to
* do it ourselves.
*/
this.isPlaying_ = false;
}

/**
* @param {Object} [options]
* @param {number} [options.volume] default 1.0, which is "no change"
* @param {boolean} [options.loop] default false
* @param {function} [options.onEnded]
*/
Sound.prototype.play = function (options) {
options = options || {};
if (!this.audioElement && !this.reusableBuffer) {
return;
}

if (this.reusableBuffer) {
this.playableBuffer = this.newPlayableBufferSource(this.reusableBuffer, options);

// Hook up on-ended callback, although browser support may be limited.
this.playableBuffer.onended = function () {
this.isPlaying_ = false;
if (options.onEnded) {
options.onEnded();
}
}.bind(this);

// Play sound, supporting older versions of the Web Audio API which used noteOn(Off).
if (this.playableBuffer.start) {
this.playableBuffer.start(0);
} else {
this.playableBuffer.noteOn(0);
}
this.isPlaying_ = true;
return;
}

if (!this.config.allowHTML5Mobile && isMobile()) {
// Don't play HTML 5 audio on mobile
return;
}

var volume = (typeof options.volume === "undefined") ? 1 :
Math.max(0, Math.min(1, options.volume));
this.audioElement.volume = volume;
this.audioElement.loop = !!options.loop;
var unregisterAndCallback = function () {
this.audioElement.removeEventListener('abort', unregisterAndCallback);
this.audioElement.removeEventListener('ended', unregisterAndCallback);
this.audioElement.removeEventListener('pause', unregisterAndCallback);
this.isPlaying_ = false;
if (options.onEnded) {
options.onEnded();
}
}.bind(this);
this.audioElement.addEventListener('abort', unregisterAndCallback);
this.audioElement.addEventListener('ended', unregisterAndCallback);
this.audioElement.addEventListener('pause', unregisterAndCallback);
this.audioElement.play();
this.isPlaying_ = true;
};

Sound.prototype.stop = function () {
try {
if (this.playableBuffer) {
if (this.playableBuffer.stop) { // Newest web audio pseudo-standard.
this.playableBuffer.stop(0);
} else if (this.playableBuffer.noteOff) { // Older web audio.
this.playableBuffer.noteOff(0);
}
} else if (this.audioElement) {
// html 5 audio.
this.audioElement.pause();
}
} catch (e) {
if (e.name === 'InvalidStateError') {
// Stopping a sound that hasn't been played.
} else {
throw e;
}
}
this.isPlaying_ = false;
};

/**
* @returns {boolean} whether the sound is currently playing.
*/
Sound.prototype.isPlaying = function () {
return this.isPlaying_;
};

Sound.prototype.newPlayableBufferSource = function (buffer, options) {
var newSound = this.audioContext.createBufferSource();

// Older versions of chrome call this createGainNode instead of createGain
if (this.audioContext.createGain) {
this.gainNode = this.audioContext.createGain();
} else if (this.audioContext.createGainNode) {
this.gainNode = this.audioContext.createGainNode();
} else {
return null;
}

newSound.buffer = buffer;
newSound.loop = !!options.loop;
newSound.connect(this.gainNode);
this.gainNode.connect(this.audioContext.destination);
var startingVolume = typeof options.volume === "undefined" ? 1 : options.volume;
this.gainNode.gain.value = startingVolume;
return newSound;
};

/**
* Do an exponential fade from the current gain to a new given value, over the
* given number of seconds.
* @param {number} gain - desired final gain value
* @param {number} durationSeconds
*/
Sound.prototype.fadeToGain = function (gain, durationSeconds) {
if (this.gainNode) {
this.fadeToGainWebAudio_(gain, durationSeconds);
} else if (this.audioElement) {
this.fadeToGainHtml5Audio_(gain, durationSeconds);
}
};

/**
* Do an exponential fade from the current gain to a new given value, over the
* given number of seconds.
* Using Web Audio (preferred, but not supported in IE)
* @param {number} gain - desired final gain value
* @param {number} durationSeconds
* @private
*/
Sound.prototype.fadeToGainWebAudio_ = function (gain, durationSeconds) {
if (!this.gainNode) {
return;
}

// Can't exponential ramp to zero, simulate by getting close.
if (gain === 0) {
gain = 0.01;
}

var currTime = this.audioContext.currentTime;
this.gainNode.gain.setValueAtTime(this.gainNode.gain.value, currTime);
this.gainNode.gain.exponentialRampToValueAtTime(gain, currTime + durationSeconds);
};

/**
* Do an exponential fade from the current gain to a new given value, over the
* given number of seconds.
* Using HTML5 Audio (fallback player)
* @param {number} gain - desired final gain value
* @param {number} durationSeconds
* @private
*/
Sound.prototype.fadeToGainHtml5Audio_ = function (gain, durationSeconds) {
if (!this.audioElement) {
return;
}

var startVolume = this.audioElement.volume || 1;
var finalVolume = Math.max(0, Math.min(1, gain));
var deltaVolume = finalVolume - startVolume;
var durationMillis = durationSeconds * 1000;
var t0 = new Date().getTime();
var fadeInterval = setInterval(function () {
var t = new Date().getTime() - t0;

// Base condition - after duration has elapsed, snap volume to final and
// clear interval
if (t >= durationMillis) {
this.audioElement.volume = finalVolume;
clearInterval(fadeInterval);
return;
}

// TODO: Probably ought to use ease out quad if delta is positive,
// TODO: so that cross-fades automatically work as expected.
// Ease in quad - the ear hears this as a "linear" fade
// y = c * (t/d)^2 + b
// b: initial value
// c: final delta
// d: duration
// t: time
var newVolume = deltaVolume * Math.pow(t / durationMillis, 2) + startVolume;
this.audioElement.volume = Math.max(0, Math.min(1, newVolume));
}.bind(this), 100);
};

Sound.prototype.getPlayableFile = function () {
// IE9 Running on Windows Server SKU can throw an exception on window.Audio
try {
if (!window.Audio) {
return false;
}

var audioTest = new window.Audio();

if (this.config.hasOwnProperty('mp3') && audioTest.canPlayType('audio/mp3')) {
return this.config.mp3;
}
if (this.config.hasOwnProperty('ogg') && audioTest.canPlayType('audio/ogg')) {
return this.config.ogg;
}
if (this.config.hasOwnProperty('wav') && audioTest.canPlayType('audio/wav')) {
return this.config.wav;
}
} catch (e) {

}

return false;
};

Sound.prototype.preload = function () {
var file = this.getPlayableFile();
if (!file) {
return;
}

if (!this.config.forceHTML5 && window.AudioContext && this.audioContext) {
var self = this;
this.preloadViaWebAudio(file, function (buffer) {
self.reusableBuffer = buffer;
});
return;
}

if (window.Audio) {
var audioElement = new window.Audio(file);
if (!audioElement || !audioElement.play) {
return;
}

if (!isIE9()) {
// Pre-cache audio
audioElement.play();
audioElement.pause();
}
this.audioElement = audioElement;

// Fire onLoad as soon as enough of the sound is loaded to play it
// all the way through.
var loadEventName = 'canplaythrough';
var eventListener = function () {
this.onSoundLoaded();
audioElement.removeEventListener(loadEventName, eventListener);
}.bind(this);
audioElement.addEventListener(loadEventName, eventListener);
}
};

Sound.prototype.onSoundLoaded = function () {
if (this.config.playAfterLoad) {
this.play(this.config.playAfterLoadOptions);
}
if (this.onLoad) {
this.onLoad();
}
};

Sound.prototype.preloadViaWebAudio = function (filename, onPreloadedCallback) {
var request = new XMLHttpRequest();
request.open('GET', filename, true);
request.responseType = 'arraybuffer';
var self = this;
request.onload = function () {
self.audioContext.decodeAudioData(request.response, function (buffer) {
onPreloadedCallback(buffer);
self.onSoundLoaded();
});
};
request.send();
};

0 comments on commit 9e6330f

Please sign in to comment.