Creates a new <video> element that contains the audio/video feed
* from a webcam. This can be drawn onto the canvas using video().
* More specific properties of the feed can be passing in a Constraints object.
* See the
* W3C
* spec for possible properties. Note that not all of these are supported
* by all browsers.
* Security note: A new browser security specification requires that getUserMedia,
* which is behind createCapture(), only works when you're running the code locally,
* or on HTTPS. Learn more here
* and here.
*
* @method createCapture
* @param {String|Constant|Object} type type of capture, either VIDEO or
* AUDIO if none specified, default both,
* or a Constraints object
* @param {Function} callback function to be called once
* stream has loaded
* @return {Object/p5.Element} capture video p5.Element
* @example
*
* var h2 = createElement('h2','im an h2 p5.element!');
*
*/
p5.prototype.createElement = function(tag, content) {
var elt = document.createElement(tag);
if (typeof content !== 'undefined') {
elt.innerHTML = content;
}
return addElement(elt, this);
};
// =============================================================================
// p5.Element additions
// =============================================================================
/**
*
* Adds specified class to the element.
*
* @for p5.Element
* @method addClass
* @param {String} class name of class to add
* @return {Object/p5.Element}
* @example
*
* var div = createDiv('div');
* div.addClass('myClass');
*
*/
p5.Element.prototype.addClass = function(c) {
if (this.elt.className) {
// PEND don't add class more than once
//var regex = new RegExp('[^a-zA-Z\d:]?'+c+'[^a-zA-Z\d:]?');
//if (this.elt.className.search(/[^a-zA-Z\d:]?hi[^a-zA-Z\d:]?/) === -1) {
this.elt.className = this.elt.className+' '+c;
//}
} else {
this.elt.className = c;
}
return this;
}
/**
*
* Removes specified class from the element.
*
* @method removeClass
* @param {String} class name of class to remove
* @return {Object/p5.Element}
*/
p5.Element.prototype.removeClass = function(c) {
var regex = new RegExp('(?:^|\\s)'+c+'(?!\\S)');
this.elt.className = this.elt.className.replace(regex, '');
this.elt.className = this.elt.className.replace(/^\s+|\s+$/g, ""); //prettify (optional)
return this;
}
/**
*
* Attaches the element as a child to the parent specified.
* Accepts either a string ID, DOM node, or p5.Element.
* If no argument is specified, an array of children DOM nodes is returned.
*
* @method child
* @param {String|Object|p5.Element} [child] the ID, DOM node, or p5.Element
* to add to the current element
* @return {p5.Element}
* @example
*
* var div0 = createDiv('this is the parent');
* var div1 = createDiv('this is the child');
* div0.child(div1); // use p5.Element
*
*
* var div0 = createDiv('this is the parent');
* var div1 = createDiv('this is the child');
* div1.id('apples');
* div0.child('apples'); // use id
*
*
* var div0 = createDiv('this is the parent');
* var elt = document.getElementById('myChildDiv');
* div0.child(elt); // use element from page
*
*/
p5.Element.prototype.child = function(c) {
if (typeof c === 'undefined'){
return this.elt.childNodes
}
if (typeof c === 'string') {
if (c[0] === '#') {
c = c.substring(1);
}
c = document.getElementById(c);
} else if (c instanceof p5.Element) {
c = c.elt;
}
this.elt.appendChild(c);
return this;
};
/**
* Centers a p5 Element either vertically, horizontally,
* or both, relative to its parent or according to
* the body if the Element has no parent. If no argument is passed
* the Element is aligned both vertically and horizontally.
*
* @param {String} align passing 'vertical', 'horizontal' aligns element accordingly
* @return {Object/p5.Element} pointer to p5.Element
* @example
*
* function setup() {
* var div = createDiv('').size(10,10);
* div.style('background-color','orange');
* div.center();
*
* }
*
*/
p5.Element.prototype.center = function(align) {
var style = this.elt.style.display;
var hidden = this.elt.style.display === 'none';
var parentHidden = this.parent().style.display === 'none';
var pos = { x : this.elt.offsetLeft, y : this.elt.offsetTop };
if (hidden) this.show();
this.elt.style.display = 'block';
this.position(0,0);
if (parentHidden) this.parent().style.display = 'block';
var wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
var hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
var y = pos.y;
var x = pos.x;
if (align === 'both' || align === undefined){
this.position(wOffset/2, hOffset/2);
}else if (align === 'horizontal'){
this.position(wOffset/2, y);
}else if (align === 'vertical'){
this.position(x, hOffset/2);
}
this.style('display', style);
if (hidden) this.hide();
if (parentHidden) this.parent().style.display = 'none';
return this;
};
/**
*
* If an argument is given, sets the inner HTML of the element,
* replacing any existing html. If true is included as a second
* argument, html is appended instead of replacing existing html.
* If no arguments are given, returns
* the inner HTML of the element.
*
* @for p5.Element
* @method html
* @param {String} [html] the HTML to be placed inside the element
* @param {boolean} [append] whether to append HTML to existing
* @return {Object/p5.Element|String}
* @example
*
* var div = createDiv('').size(100,100);
* div.html('hi');
*
*
* var div = createDiv('Hello ').size(100,100);
* div.html('World', true);
*
*/
p5.Element.prototype.html = function() {
if (arguments.length === 0) {
return this.elt.innerHTML;
} else if (arguments[1]) {
this.elt.innerHTML += arguments[0];
return this;
} else {
this.elt.innerHTML = arguments[0];
return this;
}
};
/**
*
* Sets the position of the element relative to (0, 0) of the
* window. Essentially, sets position:absolute and left and top
* properties of style. If no arguments given returns the x and y position
* of the element in an object.
*
* @method position
* @param {Number} [x] x-position relative to upper left of window
* @param {Number} [y] y-position relative to upper left of window
* @return {Object/p5.Element}
* @example
*
* function setup() {
* var cnv = createCanvas(100, 100);
* // positions canvas 50px to the right and 100px
* // below upper left corner of the window
* cnv.position(50, 100);
* }
*
*/
p5.Element.prototype.position = function() {
if (arguments.length === 0){
return { 'x' : this.elt.offsetLeft , 'y' : this.elt.offsetTop };
}else{
this.elt.style.position = 'absolute';
this.elt.style.left = arguments[0]+'px';
this.elt.style.top = arguments[1]+'px';
this.x = arguments[0];
this.y = arguments[1];
return this;
}
};
/* Helper method called by p5.Element.style() */
p5.Element.prototype._translate = function(){
this.elt.style.position = 'absolute';
// save out initial non-translate transform styling
var transform = '';
if (this.elt.style.transform) {
transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
}
if (arguments.length === 2) {
this.elt.style.transform = 'translate('+arguments[0]+'px, '+arguments[1]+'px)';
} else if (arguments.length > 2) {
this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)';
if (arguments.length === 3) {
this.elt.parentElement.style.perspective = '1000px';
} else {
this.elt.parentElement.style.perspective = arguments[3]+'px';
}
}
// add any extra transform styling back on end
this.elt.style.transform += transform;
return this;
};
/* Helper method called by p5.Element.style() */
p5.Element.prototype._rotate = function(){
// save out initial non-rotate transform styling
var transform = '';
if (this.elt.style.transform) {
var transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
}
if (arguments.length === 1){
this.elt.style.transform = 'rotate('+arguments[0]+'deg)';
}else if (arguments.length === 2){
this.elt.style.transform = 'rotate('+arguments[0]+'deg, '+arguments[1]+'deg)';
}else if (arguments.length === 3){
this.elt.style.transform = 'rotateX('+arguments[0]+'deg)';
this.elt.style.transform += 'rotateY('+arguments[1]+'deg)';
this.elt.style.transform += 'rotateZ('+arguments[2]+'deg)';
}
// add remaining transform back on
this.elt.style.transform += transform;
return this;
};
/**
* Sets the given style (css) property (1st arg) of the element with the
* given value (2nd arg). If a single argument is given, .style()
* returns the value of the given property; however, if the single argument
* is given in css syntax ('text-align:center'), .style() sets the css
* appropriatly. .style() also handles 2d and 3d css transforms. If
* the 1st arg is 'rotate', 'translate', or 'position', the following arguments
* accept Numbers as values. ('translate', 10, 100, 50);
*
* @method style
* @param {String} property property to be set
* @param {String|Number|p5.Color} [value] value to assign to property
* @param {String|Number} [value] value to assign to property (rotate/translate)
* @param {String|Number} [value] value to assign to property (rotate/translate)
* @param {String|Number} [value] value to assign to property (translate)
* @return {String|Object/p5.Element} value of property, if no value is specified
* or p5.Element
* @example
*
* var myDiv = createDiv("I like pandas.");
* myDiv.style("font-size", "18px");
* myDiv.style("color", "#ff0000");
*
*
* var col = color(25,23,200,50);
* var button = createButton("button");
* button.style("background-color", col);
* button.position(10, 10);
*
*
* var myDiv = createDiv("I like lizards.");
* myDiv.style("position", 20, 20);
* myDiv.style("rotate", 45);
*
*
* var myDiv;
* function setup() {
* background(200);
* myDiv = createDiv("I like gray.");
* myDiv.position(20, 20);
* }
*
* function draw() {
* myDiv.style("font-size", mouseX+"px");
* }
*
*/
p5.Element.prototype.style = function(prop, val) {
var self = this;
if (val instanceof p5.Color) {
val = 'rgba(' + val.levels[0] + ',' + val.levels[1] + ',' + val.levels[2] + ',' + val.levels[3]/255 + ')'
}
if (typeof val === 'undefined') {
if (prop.indexOf(':') === -1) {
var styles = window.getComputedStyle(self.elt);
var style = styles.getPropertyValue(prop);
return style;
} else {
var attrs = prop.split(';');
for (var i = 0; i < attrs.length; i++) {
var parts = attrs[i].split(':');
if (parts[0] && parts[1]) {
this.elt.style[parts[0].trim()] = parts[1].trim();
}
}
}
} else {
if (prop === 'rotate' || prop === 'translate' || prop === 'position'){
var trans = Array.prototype.shift.apply(arguments);
var f = this[trans] || this['_'+trans];
f.apply(this, arguments);
} else {
this.elt.style[prop] = val;
if (prop === 'width' || prop === 'height' || prop === 'left' || prop === 'top') {
var numVal = val.replace(/\D+/g, '');
this[prop] = parseInt(numVal, 10); // pend: is this necessary?
}
}
}
return this;
};
/**
*
* Adds a new attribute or changes the value of an existing attribute
* on the specified element. If no value is specified, returns the
* value of the given attribute, or null if attribute is not set.
*
* @method attribute
* @param {String} attr attribute to set
* @param {String} [value] value to assign to attribute
* @return {String|Object/p5.Element} value of attribute, if no value is
* specified or p5.Element
* @example
*
* var myDiv = createDiv("I like pandas.");
* myDiv.attribute("align", "center");
*
*/
p5.Element.prototype.attribute = function(attr, value) {
if (typeof value === 'undefined') {
return this.elt.getAttribute(attr);
} else {
this.elt.setAttribute(attr, value);
return this;
}
};
/**
*
* Removes an attribute on the specified element.
*
* @method removeAttribute
* @param {String} attr attribute to remove
* @return {Object/p5.Element}
*
* @example
*
* var button;
* var checkbox;
*
* function setup() {
* checkbox = createCheckbox('enable', true);
* checkbox.changed(enableButton);
* button = createButton('button');
* button.position(10, 10);
* }
*
* function enableButton() {
* if( this.checked() ) {
* // Re-enable the button
* button.removeAttribute('disabled');
* } else {
* // Disable the button
* button.attribute('disabled','');
* }
* }
*
*/
p5.Element.prototype.removeAttribute = function(attr) {
this.elt.removeAttribute(attr);
return this;
};
/**
* Either returns the value of the element if no arguments
* given, or sets the value of the element.
*
* @method value
* @param {String|Number} [value]
* @return {String|Object/p5.Element} value of element if no value is specified or p5.Element
* @example
*
* // gets the value
* var inp;
* function setup() {
* inp = createInput('');
* }
*
* function mousePressed() {
* print(inp.value());
* }
*
*
* // sets the value
* var inp;
* function setup() {
* inp = createInput('myValue');
* }
*
* function mousePressed() {
* inp.value("myValue");
* }
*
*/
p5.Element.prototype.value = function() {
if (arguments.length > 0) {
this.elt.value = arguments[0];
return this;
} else {
if (this.elt.type === 'range') {
return parseFloat(this.elt.value);
}
else return this.elt.value;
}
};
/**
*
* Shows the current element. Essentially, setting display:block for the style.
*
* @method show
* @return {Object/p5.Element}
* @example
*
* var div = createDiv('div');
* div.style("display", "none");
* div.show(); // turns display to block
*
*/
p5.Element.prototype.show = function() {
this.elt.style.display = 'block';
return this;
};
/**
* Hides the current element. Essentially, setting display:none for the style.
*
* @method hide
* @return {Object/p5.Element}
* @example
*
* var div = createDiv('this is a div');
* div.hide();
*
*/
p5.Element.prototype.hide = function() {
this.elt.style.display = 'none';
return this;
};
/**
*
* Sets the width and height of the element. AUTO can be used to
* only adjust one dimension. If no arguments given returns the width and height
* of the element in an object.
*
* @method size
* @param {Number} [w] width of the element
* @param {Number} [h] height of the element
* @return {Object/p5.Element}
* @example
*
* var div = createDiv('this is a div');
* div.size(100, 100);
*
*/
p5.Element.prototype.size = function(w, h) {
if (arguments.length === 0){
return { 'width' : this.elt.offsetWidth , 'height' : this.elt.offsetHeight };
}else{
var aW = w;
var aH = h;
var AUTO = p5.prototype.AUTO;
if (aW !== AUTO || aH !== AUTO) {
if (aW === AUTO) {
aW = h * this.width / this.height;
} else if (aH === AUTO) {
aH = w * this.height / this.width;
}
// set diff for cnv vs normal div
if (this.elt instanceof HTMLCanvasElement) {
var j = {};
var k = this.elt.getContext('2d');
for (var prop in k) {
j[prop] = k[prop];
}
this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
this.elt.setAttribute('style', 'width:' + aW + 'px; height:' + aH + 'px');
this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
for (var prop in j) {
this.elt.getContext('2d')[prop] = j[prop];
}
} else {
this.elt.style.width = aW+'px';
this.elt.style.height = aH+'px';
this.elt.width = aW;
this.elt.height = aH;
this.width = aW;
this.height = aH;
}
this.width = this.elt.offsetWidth;
this.height = this.elt.offsetHeight;
if (this._pInst) { // main canvas associated with p5 instance
if (this._pInst._curElement.elt === this.elt) {
this._pInst._setProperty('width', this.elt.offsetWidth);
this._pInst._setProperty('height', this.elt.offsetHeight);
}
}
}
return this;
}
};
/**
* Removes the element and deregisters all listeners.
* @method remove
* @example
*
* var myDiv = createDiv('this is some text');
* myDiv.remove();
*
*/
p5.Element.prototype.remove = function() {
// deregister events
for (var ev in this._events) {
this.elt.removeEventListener(ev, this._events[ev]);
}
if (this.elt.parentNode) {
this.elt.parentNode.removeChild(this.elt);
}
delete(this);
};
// =============================================================================
// p5.MediaElement additions
// =============================================================================
/**
* Extends p5.Element to handle audio and video. In addition to the methods
* of p5.Element, it also contains methods for controlling media. It is not
* called directly, but p5.MediaElements are created by calling createVideo,
* createAudio, and createCapture.
*
* @class p5.MediaElement
* @constructor
* @param {String} elt DOM node that is wrapped
* @param {Object} [pInst] pointer to p5 instance
*/
p5.MediaElement = function(elt, pInst) {
p5.Element.call(this, elt, pInst);
var self = this;
this.elt.crossOrigin = 'anonymous';
this._prevTime = 0;
this._cueIDCounter = 0;
this._cues = [];
this._pixelDensity = 1;
/**
* Path to the media element source.
*
* @property src
* @return {String} src
*/
Object.defineProperty(self, 'src', {
get: function() {
var firstChildSrc = self.elt.children[0].src;
var srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
var ret = firstChildSrc === window.location.href ? srcVal : firstChildSrc;
return ret;
},
set: function(newValue) {
for (var i = 0; i < self.elt.children.length; i++) {
self.elt.removeChild(self.elt.children[i]);
}
var source = document.createElement('source');
source.src = newValue;
elt.appendChild(source);
self.elt.src = newValue;
},
});
// private _onended callback, set by the method: onended(callback)
self._onended = function() {};
self.elt.onended = function() {
self._onended(self);
}
};
p5.MediaElement.prototype = Object.create(p5.Element.prototype);
/**
* Play an HTML5 media element.
*
* @method play
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.play = function() {
if (this.elt.currentTime === this.elt.duration) {
this.elt.currentTime = 0;
}
if (this.elt.readyState > 1) {
this.elt.play();
} else {
// in Chrome, playback cannot resume after being stopped and must reload
this.elt.load();
this.elt.play();
}
return this;
};
/**
* Stops an HTML5 media element (sets current time to zero).
*
* @method stop
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.stop = function() {
this.elt.pause();
this.elt.currentTime = 0;
return this;
};
/**
* Pauses an HTML5 media element.
*
* @method pause
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.pause = function() {
this.elt.pause();
return this;
};
/**
* Set 'loop' to true for an HTML5 media element, and starts playing.
*
* @method loop
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.loop = function() {
this.elt.setAttribute('loop', true);
this.play();
return this;
};
/**
* Set 'loop' to false for an HTML5 media element. Element will stop
* when it reaches the end.
*
* @method noLoop
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.noLoop = function() {
this.elt.setAttribute('loop', false);
return this;
};
/**
* Set HTML5 media element to autoplay or not.
*
* @method autoplay
* @param {Boolean} autoplay whether the element should autoplay
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.autoplay = function(val) {
this.elt.setAttribute('autoplay', val);
return this;
};
/**
* Sets volume for this HTML5 media element. If no argument is given,
* returns the current volume.
*
* @param {Number} [val] volume between 0.0 and 1.0
* @return {Number|p5.MediaElement} current volume or p5.MediaElement
* @method volume
*/
p5.MediaElement.prototype.volume = function(val) {
if (typeof val === 'undefined') {
return this.elt.volume;
} else {
this.elt.volume = val;
}
};
/**
* If no arguments are given, returns the current playback speed of the
* element. The speed parameter sets the speed where 2.0 will play the
* element twice as fast, 0.5 will play at half the speed, and -1 will play
* the element in normal speed in reverse.(Note that not all browsers support
* backward playback and even if they do, playback might not be smooth.)
*
* @method speed
* @param {Number} [speed] speed multiplier for element playback
* @return {Number|Object/p5.MediaElement} current playback speed or p5.MediaElement
*/
p5.MediaElement.prototype.speed = function(val) {
if (typeof val === 'undefined') {
return this.elt.playbackRate;
} else {
this.elt.playbackRate = val;
}
};
/**
* If no arguments are given, returns the current time of the element.
* If an argument is given the current time of the element is set to it.
*
* @method time
* @param {Number} [time] time to jump to (in seconds)
* @return {Number|Object/p5.MediaElement} current time (in seconds)
* or p5.MediaElement
*/
p5.MediaElement.prototype.time = function(val) {
if (typeof val === 'undefined') {
return this.elt.currentTime;
} else {
this.elt.currentTime = val;
}
};
/**
* Returns the duration of the HTML5 media element.
*
* @method duration
* @return {Number} duration
*/
p5.MediaElement.prototype.duration = function() {
return this.elt.duration;
};
p5.MediaElement.prototype.pixels = [];
p5.MediaElement.prototype.loadPixels = function() {
if (!this.canvas) {
this.canvas = document.createElement('canvas');
this.drawingContext = this.canvas.getContext('2d');
}
if (this.loadedmetadata) { // wait for metadata for w/h
if (this.canvas.width !== this.elt.width) {
this.canvas.width = this.elt.width;
this.canvas.height = this.elt.height;
this.width = this.canvas.width;
this.height = this.canvas.height;
}
this.drawingContext.drawImage(this.elt, 0, 0, this.canvas.width, this.canvas.height);
p5.Renderer2D.prototype.loadPixels.call(this);
}
return this;
}
p5.MediaElement.prototype.updatePixels = function(x, y, w, h){
if (this.loadedmetadata) { // wait for metadata
p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
}
return this;
}
p5.MediaElement.prototype.get = function(x, y, w, h){
if (this.loadedmetadata) { // wait for metadata
return p5.Renderer2D.prototype.get.call(this, x, y, w, h);
} else if (typeof x === 'undefined') {
return new p5.Image(1, 1);
} else if (w > 1) {
return new p5.Image(x, y, w, h);
} else {
return [0, 0, 0, 255];
}
};
p5.MediaElement.prototype.set = function(x, y, imgOrCol){
if (this.loadedmetadata) { // wait for metadata
p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
}
};
p5.MediaElement.prototype.copy = function(){
p5.Renderer2D.prototype.copy.apply(this, arguments);
};
p5.MediaElement.prototype.mask = function(){
this.loadPixels();
p5.Image.prototype.mask.apply(this, arguments);
};
/**
* Schedule an event to be called when the audio or video
* element reaches the end. If the element is looping,
* this will not be called. The element is passed in
* as the argument to the onended callback.
*
* @method onended
* @param {Function} callback function to call when the
* soundfile has ended. The
* media element will be passed
* in as the argument to the
* callback.
* @return {Object/p5.MediaElement}
* @example
*
* function setup() {
* audioEl = createAudio('assets/beat.mp3');
* audioEl.showControls(true);
* audioEl.onended(sayDone);
* }
*
* function sayDone(elt) {
* alert('done playing ' + elt.src );
* }
*
*/
p5.MediaElement.prototype.onended = function(callback) {
this._onended = callback;
return this;
};
/*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
/**
* Send the audio output of this element to a specified audioNode or
* p5.sound object. If no element is provided, connects to p5's master
* output. That connection is established when this method is first called.
* All connections are removed by the .disconnect() method.
*
* This method is meant to be used with the p5.sound.js addon library.
*
* @method connect
* @param {AudioNode|p5.sound object} audioNode AudioNode from the Web Audio API,
* or an object from the p5.sound library
*/
p5.MediaElement.prototype.connect = function(obj) {
var audioContext, masterOutput;
// if p5.sound exists, same audio context
if (typeof p5.prototype.getAudioContext === 'function') {
audioContext = p5.prototype.getAudioContext();
masterOutput = p5.soundOut.input;
} else {
try {
audioContext = obj.context;
masterOutput = audioContext.destination
} catch(e) {
throw 'connect() is meant to be used with Web Audio API or p5.sound.js'
}
}
// create a Web Audio MediaElementAudioSourceNode if none already exists
if (!this.audioSourceNode) {
this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
// connect to master output when this method is first called
this.audioSourceNode.connect(masterOutput);
}
// connect to object if provided
if (obj) {
if (obj.input) {
this.audioSourceNode.connect(obj.input);
} else {
this.audioSourceNode.connect(obj);
}
}
// otherwise connect to master output of p5.sound / AudioContext
else {
this.audioSourceNode.connect(masterOutput);
}
};
/**
* Disconnect all Web Audio routing, including to master output.
* This is useful if you want to re-route the output through
* audio effects, for example.
*
* @method disconnect
*/
p5.MediaElement.prototype.disconnect = function() {
if (this.audioSourceNode) {
this.audioSourceNode.disconnect();
} else {
throw 'nothing to disconnect';
}
};
/*** SHOW / HIDE CONTROLS ***/
/**
* Show the default MediaElement controls, as determined by the web browser.
*
* @method showControls
*/
p5.MediaElement.prototype.showControls = function() {
// must set style for the element to show on the page
this.elt.style['text-align'] = 'inherit';
this.elt.controls = true;
};
/**
* Hide the default mediaElement controls.
*
* @method hideControls
*/
p5.MediaElement.prototype.hideControls = function() {
this.elt.controls = false;
};
/*** SCHEDULE EVENTS ***/
/**
* Schedule events to trigger every time a MediaElement
* (audio/video) reaches a playback cue point.
*
* Accepts a callback function, a time (in seconds) at which to trigger
* the callback, and an optional parameter for the callback.
*
* Time will be passed as the first parameter to the callback function,
* and param will be the second parameter.
*
*
* @method addCue
* @param {Number} time Time in seconds, relative to this media
* element's playback. For example, to trigger
* an event every time playback reaches two
* seconds, pass in the number 2. This will be
* passed as the first parameter to
* the callback function.
* @param {Function} callback Name of a function that will be
* called at the given time. The callback will
* receive time and (optionally) param as its
* two parameters.
* @param {Object} [value] An object to be passed as the
* second parameter to the
* callback function.
* @return {Number} id ID of this cue,
* useful for removeCue(id)
* @example
*
* function setup() {
* background(255,255,255);
*
* audioEl = createAudio('assets/beat.mp3');
* audioEl.showControls();
*
* // schedule three calls to changeBackground
* audioEl.addCue(0.5, changeBackground, color(255,0,0) );
* audioEl.addCue(1.0, changeBackground, color(0,255,0) );
* audioEl.addCue(2.5, changeBackground, color(0,0,255) );
* audioEl.addCue(3.0, changeBackground, color(0,255,255) );
* audioEl.addCue(4.2, changeBackground, color(255,255,0) );
* audioEl.addCue(5.0, changeBackground, color(255,255,0) );
* }
*
* function changeBackground(val) {
* background(val);
* }
*
*/
p5.MediaElement.prototype.addCue = function(time, callback, val) {
var id = this._cueIDCounter++;
var cue = new Cue(callback, time, id, val);
this._cues.push(cue);
if (!this.elt.ontimeupdate) {
this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
}
return id;
};
/**
* Remove a callback based on its ID. The ID is returned by the
* addCue method.
*
* @method removeCue
* @param {Number} id ID of the cue, as returned by addCue
*/
p5.MediaElement.prototype.removeCue = function(id) {
for (var i = 0; i < this._cues.length; i++) {
var cue = this._cues[i];
if (cue.id === id) {
this.cues.splice(i, 1);
}
}
if (this._cues.length === 0) {
this.elt.ontimeupdate = null
}
};
/**
* Remove all of the callbacks that had originally been scheduled
* via the addCue method.
*
* @method clearCues
*/
p5.MediaElement.prototype.clearCues = function() {
this._cues = [];
this.elt.ontimeupdate = null;
};
// private method that checks for cues to be fired if events
// have been scheduled using addCue(callback, time).
p5.MediaElement.prototype._onTimeUpdate = function() {
var playbackTime = this.time();
for (var i = 0 ; i < this._cues.length; i++) {
var callbackTime = this._cues[i].time;
var val = this._cues[i].val;
if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
// pass the scheduled callbackTime as parameter to the callback
this._cues[i].callback(val);
}
}
this._prevTime = playbackTime;
};
// Cue inspired by JavaScript setTimeout, and the
// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
var Cue = function(callback, time, id, val) {
this.callback = callback;
this.time = time;
this.id = id;
this.val = val;
};
// =============================================================================
// p5.File
// =============================================================================
/**
* Base class for a file
* Using this for createFileInput
*
* @class p5.File
* @constructor
* @param {File} file File that is wrapped
* @param {Object} [pInst] pointer to p5 instance
*/
p5.File = function(file, pInst) {
/**
* Underlying File object. All normal File methods can be called on this.
*
* @property file
*/
this.file = file;
this._pInst = pInst;
// Splitting out the file type into two components
// This makes determining if image or text etc simpler
var typeList = file.type.split('/');
/**
* File type (image, text, etc.)
*
* @property type
*/
this.type = typeList[0];
/**
* File subtype (usually the file extension jpg, png, xml, etc.)
*
* @property subtype
*/
this.subtype = typeList[1];
/**
* File name
*
* @property name
*/
this.name = file.name;
/**
* File size
*
* @property size
*/
this.size = file.size;
/**
* URL string containing image data.
*
* @property data
*/
this.data = undefined;
};
}));