flocking
Version:
Creative audio synthesis for the Web
211 lines (176 loc) • 7.27 kB
JavaScript
/*
* Flocking Web Audio Input Manager
* https://github.com/continuing-creativity/flocking
*
* Copyright 2013-2014, Colin Clark
* Dual licensed under the MIT and GPL Version 2 licenses.
*/
/*global require, MediaStreamTrack, jQuery*/
/*jshint white: false, newcap: true, regexp: true, browser: true,
forin: false, nomen: true, bitwise: false, maxerr: 100,
indent: 4, plusplus: false, curly: true, eqeqeq: true,
freeze: true, latedef: true, noarg: true, nonew: true, quotmark: double, undef: true,
unused: true, strict: true, asi: false, boss: false, evil: false, expr: false,
funcscope: false*/
var fluid = fluid || require("infusion"),
flock = fluid.registerNamespace("flock");
(function () {
"use strict";
// TODO: Remove this when Chrome implements navigator.getMediaDevices().
fluid.registerNamespace("flock.webAudio.chrome");
flock.webAudio.chrome.getSources = function (callback) {
return MediaStreamTrack.getSources(function (infoSpecs) {
var normalized = fluid.transform(infoSpecs, function (infoSpec) {
infoSpec.deviceId = infoSpec.id;
return infoSpec;
});
callback(normalized);
});
};
flock.webAudio.mediaStreamFailure = function (onError) {
var msg = "This browser does not support getUserMedia() or the Media Streams API.";
if (!onError) {
fluid.log(fluid.logLevel.IMPORTANT, msg);
} else {
onError(new Error(msg));
}
};
var webAudioShims = {
AudioContext: window.AudioContext || window.webkitAudioContext,
getUserMedia: function (constraints, onAccess, onError) {
if (navigator.mediaDevices) {
var p = navigator.mediaDevices.getUserMedia(constraints);
p.then(onAccess).catch(onError);
} else if (navigator.getUserMedia) {
navigator.getUserMedia(constraints, onAccess, onError);
} else {
flock.webAudio.mediaStreamFailure(onError);
}
},
getMediaDevicesImpl: navigator.getMediaDevices ? navigator.getMediaDevices :
typeof window.MediaStreamTrack !== "undefined" ?
flock.webAudio.chrome.getSources : flock.webAudio.mediaStreamFailure,
getMediaDevice: function () {
flock.shim.getMediaDevicesImpl.apply(navigator, arguments);
}
};
jQuery.extend(flock.shim, webAudioShims);
/**
* Manages audio input devices using the Web Audio API.
*/
// Add a means for disconnecting audio input nodes.
fluid.defaults("flock.webAudio.inputDeviceManager", {
gradeNames: ["fluid.component"],
invokers: {
/**
* Opens the specified audio device.
* If no device is specified, the default device is opened.
*
* @param {Object} deviceSpec a device spec containing, optionally, an 'id' or 'label' parameter
*/
openAudioDevice: {
funcName: "flock.webAudio.inputDeviceManager.openAudioDevice",
args: [
"{arguments}.0",
"{that}.openAudioDeviceWithId",
"{that}.openFirstAudioDeviceWithLabel",
"{that}.openAudioDeviceWithConstraints"
]
},
/**
* Opens an audio device with the specified WebRTC constraints.
* If no constraints are specified, the default audio device is opened.
*
* @param {Object} constraints a WebRTC-compatible constraints object
*/
openAudioDeviceWithConstraints: {
funcName: "flock.webAudio.inputDeviceManager.openAudioDeviceWithConstraints",
args: [
"{audioSystem}.context",
"{enviro}",
"{nativeNodeManager}.createMediaStreamInput",
"{arguments}.0"
]
},
/**
* Opens an audio device with the specified WebRTC device id.
*
* @param {string} id a device identifier
*/
openAudioDeviceWithId: {
funcName: "flock.webAudio.inputDeviceManager.openAudioDeviceWithId",
args: ["{arguments}.0", "{that}.openAudioDeviceWithConstraints"]
},
/**
* Opens the first audio device found with the specified label.
* The label must be an exact, case-insensitive match.
*
* @param {string} label a device label
*/
openFirstAudioDeviceWithLabel: {
funcName: "flock.webAudio.inputDeviceManager.openFirstAudioDeviceWithLabel",
args: ["{arguments}.0", "{that}.openAudioDeviceWithId"]
}
}
});
flock.webAudio.inputDeviceManager.openAudioDevice = function (sourceSpec, idOpener, labelOpener, specOpener) {
if (sourceSpec) {
if (sourceSpec.id) {
return idOpener(sourceSpec.id);
} else if (sourceSpec.label) {
return labelOpener(sourceSpec.label);
}
}
return specOpener();
};
flock.webAudio.inputDeviceManager.openAudioDeviceWithId = function (id, deviceOpener) {
var options = {
audio: {
optional: [
{
sourceId: id
}
]
}
};
deviceOpener(options);
};
flock.webAudio.inputDeviceManager.openFirstAudioDeviceWithLabel = function (label, deviceOpener) {
if (!label) {
return;
}
// TODO: Can't access device labels until the user agrees
// to allow access to the current device.
flock.shim.getMediaDevices(function (deviceInfoSpecs) {
var matches = deviceInfoSpecs.filter(function (device) {
if (device.label.toLowerCase() === label.toLowerCase()) {
return true;
}
});
if (matches.length > 0) {
deviceOpener(matches[0].deviceId);
} else {
fluid.log(fluid.logLevel.IMPORTANT,
"An audio device named '" + label + "' could not be found.");
}
});
};
flock.webAudio.inputDeviceManager.openAudioDeviceWithConstraints = function (context, enviro, openMediaStream, options) {
options = options || {
audio: true
};
// Acquire an input bus ahead of time so we can synchronously
// notify the client where its output will be.
var busNum = enviro.busManager.acquireNextBus("input");
function error (err) {
fluid.log(fluid.logLevel.IMPORTANT,
"An error occurred while trying to access the user's microphone. " +
err);
}
function success (mediaStream) {
openMediaStream(mediaStream, busNum);
}
flock.shim.getUserMedia(options, success, error);
return busNum;
};
}());