/*! p5.js v0.9.0 July 01, 2019 */ /** *
The web is much more than just canvas and p5.dom makes it easy to interact * with other HTML5 objects, including text, hyperlink, image, input, video, * audio, and webcam.
*There is a set of creation methods, DOM manipulation methods, and * an extended p5.Element that supports a range of HTML elements. See the * * beyond the canvas tutorial for a full overview of how this addon works. * *
Methods and properties shown in black are part of the p5.js core, items in * blue are part of the p5.dom library. You will need to include an extra file * in order to access the blue functions. See the * using a library * section for information on how to include this library. p5.dom comes with * p5 complete or you can download the single file * * here.
*See tutorial: beyond the canvas * for more info on how to use this library. * * @module p5.dom * @submodule p5.dom * @for p5 * @main */ (function(root, factory) { if (typeof define === 'function' && define.amd) define('p5.dom', ['p5'], function(p5) { factory(p5); }); else if (typeof exports === 'object') factory(require('../p5')); else factory(root['p5']); })(this, function(p5) { // ============================================================================= // p5 additions // ============================================================================= /** * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' * prefixes to specify an ID or class respectively, and none for a tag) and returns it as * a p5.Element. If a class or tag name is given with more than 1 element, * only the first element will be returned. * The DOM node itself can be accessed with .elt. * Returns null if none found. You can also specify a container to search within. * * @method select * @param {String} name id, class, or tag name of element to search for * @param {String|p5.Element|HTMLElement} [container] id, p5.Element, or * HTML element to search within * @return {p5.Element|null} p5.Element containing node found * @example *
* function setup() {
* createCanvas(100, 100);
* //translates canvas 50px down
* select('canvas').position(100, 100);
* }
*
* // these are all valid calls to select()
* var a = select('#moo');
* var b = select('#blah', '#myContainer');
* var c, e;
* if (b) {
* c = select('#foo', b);
* }
* var d = document.getElementById('beep');
* if (d) {
* e = select('p', d);
* }
* [a, b, c, d, e]; // unused
*
* function setup() {
* createButton('btn');
* createButton('2nd btn');
* createButton('3rd btn');
* var buttons = selectAll('button');
*
* for (var i = 0; i < buttons.length; i++) {
* buttons[i].size(100, 100);
* }
* }
*
* // these are all valid calls to selectAll()
* var a = selectAll('.moo');
* a = selectAll('div');
* a = selectAll('button', '#myContainer');
*
* var d = select('#container');
* a = selectAll('p', d);
*
* var f = document.getElementById('beep');
* a = select('.blah', f);
*
* a; // unused
*
* function setup() {
* createCanvas(100, 100);
* createDiv('this is some text');
* createP('this is a paragraph');
* }
* function mousePressed() {
* removeElements(); // this will remove the div and p, not canvas
* }
*
* var sel;
*
* function setup() {
* textAlign(CENTER);
* background(200);
* sel = createSelect();
* sel.position(10, 10);
* sel.option('pear');
* sel.option('kiwi');
* sel.option('grape');
* sel.changed(mySelectEvent);
* }
*
* function mySelectEvent() {
* var item = sel.value();
* background(200);
* text("it's a " + item + '!', 50, 50);
* }
*
* var checkbox;
* var cnv;
*
* function setup() {
* checkbox = createCheckbox(' fill');
* checkbox.changed(changeFill);
* cnv = createCanvas(100, 100);
* cnv.position(0, 30);
* noFill();
* }
*
* function draw() {
* background(200);
* ellipse(50, 50, 50, 50);
* }
*
* function changeFill() {
* if (checkbox.checked()) {
* fill(0);
* } else {
* noFill();
* }
* }
*
* // Open your console to see the output
* function setup() {
* var inp = createInput('');
* inp.input(myInputEvent);
* }
*
* function myInputEvent() {
* console.log('you are typing: ', this.value());
* }
*
* createDiv('this is some text');
*
* createP('this is some text');
*
* createSpan('this is some text');
*
* createImg('http://p5js.org/img/asterisk-01.png');
*
* createA('http://p5js.org/', 'this is a link');
*
* var slider;
* function setup() {
* slider = createSlider(0, 255, 100);
* slider.position(10, 10);
* slider.style('width', '80px');
* }
*
* function draw() {
* var val = slider.value();
* background(val);
* }
*
* var slider;
* function setup() {
* colorMode(HSB);
* slider = createSlider(0, 360, 60, 40);
* slider.position(10, 10);
* slider.style('width', '80px');
* }
*
* function draw() {
* var val = slider.value();
* background(val, 100, 100, 1);
* }
*
* var button;
* function setup() {
* createCanvas(100, 100);
* background(0);
* button = createButton('click me');
* button.position(19, 19);
* button.mousePressed(changeBG);
* }
*
* function changeBG() {
* var val = random(255);
* background(val);
* }
*
* var checkbox;
*
* function setup() {
* checkbox = createCheckbox('label', false);
* checkbox.changed(myCheckedEvent);
* }
*
* function myCheckedEvent() {
* if (this.checked()) {
* console.log('Checking!');
* } else {
* console.log('Unchecking!');
* }
* }
*
* var sel;
*
* function setup() {
* textAlign(CENTER);
* background(200);
* sel = createSelect();
* sel.position(10, 10);
* sel.option('pear');
* sel.option('kiwi');
* sel.option('grape');
* sel.changed(mySelectEvent);
* }
*
* function mySelectEvent() {
* var item = sel.value();
* background(200);
* text('It is a ' + item + '!', 50, 50);
* }
*
* var radio;
*
* function setup() {
* radio = createRadio();
* radio.option('black');
* radio.option('white');
* radio.option('gray');
* radio.style('width', '60px');
* textAlign(CENTER);
* fill(255, 0, 0);
* }
*
* function draw() {
* var val = radio.value();
* background(val);
* text(val, width / 2, height / 2);
* }
*
* var radio;
*
* function setup() {
* radio = createRadio();
* radio.option('apple', 1);
* radio.option('bread', 2);
* radio.option('juice', 3);
* radio.style('width', '60px');
* textAlign(CENTER);
* }
*
* function draw() {
* background(200);
* var val = radio.value();
* if (val) {
* text('item cost is $' + val, width / 2, height / 2);
* }
* }
*
* var inp1, inp2;
* function setup() {
* createCanvas(100, 100);
* background('grey');
* inp1 = createColorPicker('#ff0000');
* inp2 = createColorPicker(color('yellow'));
* inp1.input(setShade1);
* inp2.input(setShade2);
* setMidShade();
* }
*
* function setMidShade() {
* // Finding a shade between the two
* var commonShade = lerpColor(inp1.color(), inp2.color(), 0.5);
* fill(commonShade);
* rect(20, 20, 60, 60);
* }
*
* function setShade1() {
* setMidShade();
* console.log('You are choosing shade 1 to be : ', this.value());
* }
* function setShade2() {
* setMidShade();
* console.log('You are choosing shade 2 to be : ', this.value());
* }
*
*
* function setup() {
* var inp = createInput('');
* inp.input(myInputEvent);
* }
*
* function myInputEvent() {
* console.log('you are typing: ', this.value());
* }
*
* let input;
* let img;
*
* function setup() {
* input = createFileInput(handleFile);
* input.position(0, 0);
* }
*
* function draw() {
* background(255);
* if (img) {
* image(img, 0, 0, width, height);
* }
* }
*
* function handleFile(file) {
* print(file);
* if (file.type === 'image') {
* img = createImg(file.data);
* img.hide();
* } else {
* img = null;
* }
* }
*
* var vid;
* function setup() {
* noCanvas();
*
* vid = createVideo(
* ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm'],
* vidLoad
* );
*
* vid.size(100, 100);
* }
*
* // This function is called when the video loads
* function vidLoad() {
* vid.loop();
* vid.volume(0);
* }
*
* var ele;
* function setup() {
* ele = createAudio('assets/beat.mp3');
*
* // here we set the element to autoplay
* // The element will play as soon
* // as it is able to do so.
* ele.autoplay(true);
* }
*
Creates a new HTML5 <video> element that contains the audio/video * feed from a webcam. The element is separate from the canvas and is * displayed by default. The element can be hidden using .hide(). The feed * can be drawn onto the canvas using image(). The loadedmetadata property can * be used to detect when the element has fully loaded (see second example).
*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 {p5.Element} capture video p5.Element * @example *
* var capture;
*
* function setup() {
* createCanvas(480, 480);
* capture = createCapture(VIDEO);
* capture.hide();
* }
*
* function draw() {
* image(capture, 0, 0, width, width * capture.height / capture.width);
* filter(INVERT);
* }
*
* function setup() {
* createCanvas(480, 120);
* var constraints = {
* video: {
* mandatory: {
* minWidth: 1280,
* minHeight: 720
* },
* optional: [{ maxFrameRate: 10 }]
* },
* audio: true
* };
* createCapture(constraints, function(stream) {
* console.log(stream);
* });
* }
*
* var capture;
*
* function setup() {
* createCanvas(640, 480);
* capture = createCapture(VIDEO);
* }
* function draw() {
* background(0);
* if (capture.loadedmetadata) {
* var c = capture.get(0, 0, 100, 100);
* image(c, 0, 0);
* }
* }
*
*/
p5.prototype.createCapture = function() {
p5._validateParameters('createCapture', arguments);
var useVideo = true;
var useAudio = true;
var constraints;
var cb;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] === p5.prototype.VIDEO) {
useAudio = false;
} else if (arguments[i] === p5.prototype.AUDIO) {
useVideo = false;
} else if (typeof arguments[i] === 'object') {
constraints = arguments[i];
} else if (typeof arguments[i] === 'function') {
cb = arguments[i];
}
}
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
var elt = document.createElement('video');
// required to work in iOS 11 & up:
elt.setAttribute('playsinline', '');
if (!constraints) {
constraints = { video: useVideo, audio: useAudio };
}
navigator.mediaDevices.getUserMedia(constraints).then(
function(stream) {
try {
if ('srcObject' in elt) {
elt.srcObject = stream;
} else {
elt.src = window.URL.createObjectURL(stream);
}
} catch (err) {
elt.src = stream;
}
},
function(e) {
console.log(e);
}
);
} else {
throw 'getUserMedia not supported in this browser';
}
var c = addElement(elt, this, true);
c.loadedmetadata = false;
// set width and height onload metadata
elt.addEventListener('loadedmetadata', function() {
elt.play();
if (elt.width) {
c.width = elt.videoWidth = elt.width;
c.height = elt.videoHeight = elt.height;
} else {
c.width = c.elt.width = elt.videoWidth;
c.height = c.elt.height = elt.videoHeight;
}
c.loadedmetadata = true;
if (cb) {
cb(elt.srcObject);
}
});
return c;
};
/**
* Creates element with given tag in the DOM with given content.
* Appends to the container node if one is specified, otherwise
* appends to body.
*
* @method createElement
* @param {String} tag tag for the new element
* @param {String} [content] html content to be inserted into the element
* @return {p5.Element} pointer to p5.Element holding created node
* @example
*
* createElement('h2', 'im an h2 p5.element!');
*
*/
p5.prototype.createElement = function(tag, content) {
p5._validateParameters('createElement', arguments);
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
* @chainable
* @example
*
* var div = createDiv('div');
* div.addClass('myClass');
*
*/
p5.Element.prototype.addClass = function(c) {
if (this.elt.className) {
if (!this.hasClass(c)) {
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
* @chainable
* @example
*
* // In this example, a class is set when the div is created
* // and removed when mouse is pressed. This could link up
* // with a CSS style rule to toggle style properties.
*
* var div;
*
* function setup() {
* div = createDiv('div');
* div.addClass('myClass');
* }
*
* function mousePressed() {
* div.removeClass('myClass');
* }
*
*/
p5.Element.prototype.removeClass = function(c) {
// Note: Removing a class that does not exist does NOT throw an error in classList.remove method
this.elt.classList.remove(c);
return this;
};
/**
*
* Checks if specified class already set to element
*
* @method hasClass
* @returns {boolean} a boolean value if element has specified class
* @param c {String} class name of class to check
* @example
*
* var div;
*
* function setup() {
* div = createDiv('div');
* div.addClass('show');
* }
*
* function mousePressed() {
* if (div.hasClass('show')) {
* div.addClass('show');
* } else {
* div.removeClass('show');
* }
* }
*
*/
p5.Element.prototype.hasClass = function(c) {
return this.elt.classList.contains(c);
};
/**
*
* Toggles element class
*
* @method toggleClass
* @param c {String} class name to toggle
* @chainable
* @example
*
* var div;
*
* function setup() {
* div = createDiv('div');
* div.addClass('show');
* }
*
* function mousePressed() {
* div.toggleClass('show');
* }
*
*/
p5.Element.prototype.toggleClass = function(c) {
// classList also has a toggle() method, but we cannot use that yet as support is unclear.
// See https://github.com/processing/p5.js/issues/3631
// this.elt.classList.toggle(c);
if (this.elt.classList.contains(c)) {
this.elt.classList.remove(c);
} else {
this.elt.classList.add(c);
}
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
* @returns {Node[]} an array of child nodes
* @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
*
*
* // this example assumes there is a div already on the page
* // with id "myChildDiv"
* var div0 = createDiv('this is the parent');
* var elt = document.getElementById('myChildDiv');
* div0.child(elt); // use element from page
*
*/
/**
* @method child
* @param {String|p5.Element} [child] the ID, DOM node, or p5.Element
* to add to the current element
* @chainable
*/
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.
*
* @method center
* @param {String} [align] passing 'vertical', 'horizontal' aligns element accordingly
* @chainable
*
* @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
* @returns {String} the inner HTML of the element
* @example
*
* var div = createDiv('').size(100, 100);
* div.html('hi');
*
*
* var div = createDiv('Hello ').size(100, 100);
* div.html('World', true);
*
*/
/**
* @method html
* @param {String} [html] the HTML to be placed inside the element
* @param {boolean} [append] whether to append HTML to existing
* @chainable
*/
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
* @returns {Object} the x and y position of the element in an object
* @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);
* }
*
*/
/**
* @method position
* @param {Number} [x] x-position relative to upper left of window
* @param {Number} [y] y-position relative to upper left of window
* @chainable
*/
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) {
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
* appropriately.
*
* @method style
* @param {String} property property to be set
* @returns {String} value of property
* @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;
* function setup() {
* background(200);
* myDiv = createDiv('I like gray.');
* myDiv.position(20, 20);
* }
*
* function draw() {
* myDiv.style('font-size', mouseX + 'px');
* }
*
*/
/**
* @method style
* @param {String} property
* @param {String|Number|p5.Color} value value to assign to property
* @return {String} current value of property, if no value is given as second argument
* @chainable
*/
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') {
// input provided as single line string
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 {
// input provided as key,val pair
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);
}
}
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
* @return {String} value of attribute
*
* @example
*
* var myDiv = createDiv('I like pandas.');
* myDiv.attribute('align', 'center');
*
*/
/**
* @method attribute
* @param {String} attr attribute to set
* @param {String} value value to assign to attribute
* @chainable
*/
p5.Element.prototype.attribute = function(attr, value) {
//handling for checkboxes and radios to ensure options get
//attributes not divs
if (
this.elt.firstChild != null &&
(this.elt.firstChild.type === 'checkbox' ||
this.elt.firstChild.type === 'radio')
) {
if (typeof value === 'undefined') {
return this.elt.firstChild.getAttribute(attr);
} else {
for (var i = 0; i < this.elt.childNodes.length; i++) {
this.elt.childNodes[i].setAttribute(attr, value);
}
}
} else 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
* @chainable
*
* @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) {
if (
this.elt.firstChild != null &&
(this.elt.firstChild.type === 'checkbox' ||
this.elt.firstChild.type === 'radio')
) {
for (var i = 0; i < this.elt.childNodes.length; i++) {
this.elt.childNodes[i].removeAttribute(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
* @return {String|Number} value of the 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');
* }
*
*/
/**
* @method value
* @param {String|Number} value
* @chainable
*/
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
* @chainable
* @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
* @chainable
* @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 at a time. If no arguments are given, it
* returns the width and height of the element in an object. In case of
* elements which need to be loaded, such as images, it is recommended
* to call the function after the element has finished loading.
*
* @method size
* @return {Object} the width and height of the element in an object
* @example
*
* let div = createDiv('this is a div');
* div.size(100, 100);
* let img = createImg('assets/laDefense.jpg', () => {
* img.size(10, AUTO);
* });
*
*/
/**
* @method size
* @param {Number|Constant} w width of the element, either AUTO, or a number
* @param {Number|Constant} [h] height of the element, either AUTO, or a number
* @chainable
*/
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');
var prop;
for (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.style.width = aW + 'px';
this.elt.style.height = aH + 'px';
this._pInst.scale(
this._pInst._pixelDensity,
this._pInst._pixelDensity
);
for (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 = this.elt.offsetWidth;
this.height = this.elt.offsetHeight;
if (this._pInst && this._pInst._curElement) {
// 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;
};
/**
* Registers a callback that gets called every time a file that is
* dropped on the element has been loaded.
* p5 will load every dropped file into memory and pass it as a p5.File object to the callback.
* Multiple files dropped at the same time will result in multiple calls to the callback.
*
* You can optionally pass a second callback which will be registered to the raw
* drop event.
* The callback will thus be provided the original
* DragEvent.
* Dropping multiple files at the same time will trigger the second callback once per drop,
* whereas the first callback will trigger for each loaded file.
*
* @method drop
* @param {Function} callback callback to receive loaded file, called for each file dropped.
* @param {Function} [fxn] callback triggered once when files are dropped with the drop event.
* @chainable
* @example
*
* function setup() {
* var c = createCanvas(100, 100);
* background(200);
* textAlign(CENTER);
* text('drop file', width / 2, height / 2);
* c.drop(gotFile);
* }
*
* function gotFile(file) {
* background(200);
* text('received file:', width / 2, height / 2);
* text(file.name, width / 2, height / 2 + 50);
* }
*
*
*
* var img;
*
* function setup() {
* var c = createCanvas(100, 100);
* background(200);
* textAlign(CENTER);
* text('drop image', width / 2, height / 2);
* c.drop(gotFile);
* }
*
* function draw() {
* if (img) {
* image(img, 0, 0, width, height);
* }
* }
*
* function gotFile(file) {
* img = createImg(file.data).hide();
* }
*
*
* @alt
* Canvas turns into whatever image is dragged/dropped onto it.
*/
p5.Element.prototype.drop = function(callback, fxn) {
// Is the file stuff supported?
if (window.File && window.FileReader && window.FileList && window.Blob) {
if (!this._dragDisabled) {
this._dragDisabled = true;
var preventDefault = function(evt) {
evt.preventDefault();
};
// If you want to be able to drop you've got to turn off
// a lot of default behavior.
// avoid `attachListener` here, since it overrides other handlers.
this.elt.addEventListener('dragover', preventDefault);
// If this is a drag area we need to turn off the default behavior
this.elt.addEventListener('dragleave', preventDefault);
}
// Deal with the files
p5.Element._attachListener(
'drop',
function(evt) {
evt.preventDefault();
// Call the second argument as a callback that receives the raw drop event
if (typeof fxn === 'function') {
fxn.call(this, evt);
}
// A FileList
var files = evt.dataTransfer.files;
// Load each one and trigger the callback
for (var i = 0; i < files.length; i++) {
var f = files[i];
p5.File._load(f, callback);
}
},
this
);
} else {
console.log('The File APIs are not fully supported in this browser.');
}
return 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
*/
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._pixelsState = this;
this._pixelDensity = 1;
this._modified = false;
this._pixelsDirty = true;
this._pixelsTime = -1; // the time at which we last updated 'pixels'
/**
* Path to the media element source.
*
* @property src
* @return {String} src
* @example
*
* var ele;
*
* function setup() {
* background(250);
*
* //p5.MediaElement objects are usually created
* //by calling the createAudio(), createVideo(),
* //and createCapture() functions.
*
* //In this example we create
* //a new p5.MediaElement via createAudio().
* ele = createAudio('assets/beat.mp3');
*
* //We'll set up our example so that
* //when you click on the text,
* //an alert box displays the MediaElement's
* //src field.
* textAlign(CENTER);
* text('Click Me!', width / 2, height / 2);
* }
*
* function mouseClicked() {
* //here we test if the mouse is over the
* //canvas element when it's clicked
* if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
* //Show our p5.MediaElement's src field
* alert(ele.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;
self.modified = true;
}
});
// 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
* @chainable
* @example
*
* var ele;
*
* function setup() {
* //p5.MediaElement objects are usually created
* //by calling the createAudio(), createVideo(),
* //and createCapture() functions.
*
* //In this example we create
* //a new p5.MediaElement via createAudio().
* ele = createAudio('assets/beat.mp3');
*
* background(250);
* textAlign(CENTER);
* text('Click to Play!', width / 2, height / 2);
* }
*
* function mouseClicked() {
* //here we test if the mouse is over the
* //canvas element when it's clicked
* if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
* //Here we call the play() function on
* //the p5.MediaElement we created above.
* //This will start the audio sample.
* ele.play();
*
* background(200);
* text('You clicked Play!', width / 2, height / 2);
* }
* }
*
*/
p5.MediaElement.prototype.play = function() {
if (this.elt.currentTime === this.elt.duration) {
this.elt.currentTime = 0;
}
var promise;
if (this.elt.readyState > 1) {
promise = this.elt.play();
} else {
// in Chrome, playback cannot resume after being stopped and must reload
this.elt.load();
promise = this.elt.play();
}
if (promise && promise.catch) {
promise.catch(function(e) {
console.log(
'WARN: Element play method raised an error asynchronously',
e
);
});
}
return this;
};
/**
* Stops an HTML5 media element (sets current time to zero).
*
* @method stop
* @chainable
* @example
*
* //This example both starts
* //and stops a sound sample
* //when the user clicks the canvas
*
* //We will store the p5.MediaElement
* //object in here
* var ele;
*
* //while our audio is playing,
* //this will be set to true
* var sampleIsPlaying = false;
*
* function setup() {
* //Here we create a p5.MediaElement object
* //using the createAudio() function.
* ele = createAudio('assets/beat.mp3');
* background(200);
* textAlign(CENTER);
* text('Click to play!', width / 2, height / 2);
* }
*
* function mouseClicked() {
* //here we test if the mouse is over the
* //canvas element when it's clicked
* if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
* background(200);
*
* if (sampleIsPlaying) {
* //if the sample is currently playing
* //calling the stop() function on
* //our p5.MediaElement will stop
* //it and reset its current
* //time to 0 (i.e. it will start
* //at the beginning the next time
* //you play it)
* ele.stop();
*
* sampleIsPlaying = false;
* text('Click to play!', width / 2, height / 2);
* } else {
* //loop our sound element until we
* //call ele.stop() on it.
* ele.loop();
*
* sampleIsPlaying = true;
* text('Click to stop!', width / 2, height / 2);
* }
* }
* }
*
*/
p5.MediaElement.prototype.stop = function() {
this.elt.pause();
this.elt.currentTime = 0;
return this;
};
/**
* Pauses an HTML5 media element.
*
* @method pause
* @chainable
* @example
*
* //This example both starts
* //and pauses a sound sample
* //when the user clicks the canvas
*
* //We will store the p5.MediaElement
* //object in here
* var ele;
*
* //while our audio is playing,
* //this will be set to true
* var sampleIsPlaying = false;
*
* function setup() {
* //Here we create a p5.MediaElement object
* //using the createAudio() function.
* ele = createAudio('assets/lucky_dragons.mp3');
* background(200);
* textAlign(CENTER);
* text('Click to play!', width / 2, height / 2);
* }
*
* function mouseClicked() {
* //here we test if the mouse is over the
* //canvas element when it's clicked
* if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
* background(200);
*
* if (sampleIsPlaying) {
* //Calling pause() on our
* //p5.MediaElement will stop it
* //playing, but when we call the
* //loop() or play() functions
* //the sample will start from
* //where we paused it.
* ele.pause();
*
* sampleIsPlaying = false;
* text('Click to resume!', width / 2, height / 2);
* } else {
* //loop our sound element until we
* //call ele.pause() on it.
* ele.loop();
*
* sampleIsPlaying = true;
* text('Click to pause!', width / 2, height / 2);
* }
* }
* }
*
*/
p5.MediaElement.prototype.pause = function() {
this.elt.pause();
return this;
};
/**
* Set 'loop' to true for an HTML5 media element, and starts playing.
*
* @method loop
* @chainable
* @example
*
* //Clicking the canvas will loop
* //the audio sample until the user
* //clicks again to stop it
*
* //We will store the p5.MediaElement
* //object in here
* var ele;
*
* //while our audio is playing,
* //this will be set to true
* var sampleIsLooping = false;
*
* function setup() {
* //Here we create a p5.MediaElement object
* //using the createAudio() function.
* ele = createAudio('assets/lucky_dragons.mp3');
* background(200);
* textAlign(CENTER);
* text('Click to loop!', width / 2, height / 2);
* }
*
* function mouseClicked() {
* //here we test if the mouse is over the
* //canvas element when it's clicked
* if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
* background(200);
*
* if (!sampleIsLooping) {
* //loop our sound element until we
* //call ele.stop() on it.
* ele.loop();
*
* sampleIsLooping = true;
* text('Click to stop!', width / 2, height / 2);
* } else {
* ele.stop();
*
* sampleIsLooping = false;
* text('Click to loop!', width / 2, height / 2);
* }
* }
* }
*
*/
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
* @chainable
* @example
*
* //This example both starts
* //and stops loop of sound sample
* //when the user clicks the canvas
*
* //We will store the p5.MediaElement
* //object in here
* var ele;
* //while our audio is playing,
* //this will be set to true
* var sampleIsPlaying = false;
*
* function setup() {
* //Here we create a p5.MediaElement object
* //using the createAudio() function.
* ele = createAudio('assets/beat.mp3');
* background(200);
* textAlign(CENTER);
* text('Click to play!', width / 2, height / 2);
* }
*
* function mouseClicked() {
* //here we test if the mouse is over the
* //canvas element when it's clicked
* if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
* background(200);
*
* if (sampleIsPlaying) {
* ele.noLoop();
* text('No more Loops!', width / 2, height / 2);
* } else {
* ele.loop();
* sampleIsPlaying = true;
* text('Click to stop looping!', width / 2, height / 2);
* }
* }
* }
*
*
*/
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
* @chainable
*/
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.
*
* @method volume
* @return {Number} current volume
*
* @example
*
* var ele;
* function setup() {
* // p5.MediaElement objects are usually created
* // by calling the createAudio(), createVideo(),
* // and createCapture() functions.
* // In this example we create
* // a new p5.MediaElement via createAudio().
* ele = createAudio('assets/lucky_dragons.mp3');
* background(250);
* textAlign(CENTER);
* text('Click to Play!', width / 2, height / 2);
* }
* function mouseClicked() {
* // Here we call the volume() function
* // on the sound element to set its volume
* // Volume must be between 0.0 and 1.0
* ele.volume(0.2);
* ele.play();
* background(200);
* text('You clicked Play!', width / 2, height / 2);
* }
*
*
* var audio;
* var counter = 0;
*
* function loaded() {
* audio.play();
* }
*
* function setup() {
* audio = createAudio('assets/lucky_dragons.mp3', loaded);
* textAlign(CENTER);
* }
*
* function draw() {
* if (counter === 0) {
* background(0, 255, 0);
* text('volume(0.9)', width / 2, height / 2);
* } else if (counter === 1) {
* background(255, 255, 0);
* text('volume(0.5)', width / 2, height / 2);
* } else if (counter === 2) {
* background(255, 0, 0);
* text('volume(0.1)', width / 2, height / 2);
* }
* }
*
* function mousePressed() {
* counter++;
* if (counter === 0) {
* audio.volume(0.9);
* } else if (counter === 1) {
* audio.volume(0.5);
* } else if (counter === 2) {
* audio.volume(0.1);
* } else {
* counter = 0;
* audio.volume(0.9);
* }
* }
*
*
*/
/**
* @method volume
* @param {Number} val volume between 0.0 and 1.0
* @chainable
*/
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
* @return {Number} current playback speed of the element
*
* @example
*
* //Clicking the canvas will loop
* //the audio sample until the user
* //clicks again to stop it
*
* //We will store the p5.MediaElement
* //object in here
* var ele;
* var button;
*
* function setup() {
* createCanvas(710, 400);
* //Here we create a p5.MediaElement object
* //using the createAudio() function.
* ele = createAudio('assets/beat.mp3');
* ele.loop();
* background(200);
*
* button = createButton('2x speed');
* button.position(100, 68);
* button.mousePressed(twice_speed);
*
* button = createButton('half speed');
* button.position(200, 68);
* button.mousePressed(half_speed);
*
* button = createButton('reverse play');
* button.position(300, 68);
* button.mousePressed(reverse_speed);
*
* button = createButton('STOP');
* button.position(400, 68);
* button.mousePressed(stop_song);
*
* button = createButton('PLAY!');
* button.position(500, 68);
* button.mousePressed(play_speed);
* }
*
* function twice_speed() {
* ele.speed(2);
* }
*
* function half_speed() {
* ele.speed(0.5);
* }
*
* function reverse_speed() {
* ele.speed(-1);
* }
*
* function stop_song() {
* ele.stop();
* }
*
* function play_speed() {
* ele.play();
* }
*
*/
/**
* @method speed
* @param {Number} speed speed multiplier for element playback
* @chainable
*/
p5.MediaElement.prototype.speed = function(val) {
if (typeof val === 'undefined') {
return this.presetPlaybackRate || this.elt.playbackRate;
} else {
if (this.loadedmetadata) {
this.elt.playbackRate = val;
} else {
this.presetPlaybackRate = 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
* @return {Number} current time (in seconds)
*
* @example
*
* var ele;
* var beginning = true;
* function setup() {
* //p5.MediaElement objects are usually created
* //by calling the createAudio(), createVideo(),
* //and createCapture() functions.
*
* //In this example we create
* //a new p5.MediaElement via createAudio().
* ele = createAudio('assets/lucky_dragons.mp3');
* background(250);
* textAlign(CENTER);
* text('start at beginning', width / 2, height / 2);
* }
*
* // this function fires with click anywhere
* function mousePressed() {
* if (beginning === true) {
* // here we start the sound at the beginning
* // time(0) is not necessary here
* // as this produces the same result as
* // play()
* ele.play().time(0);
* background(200);
* text('jump 2 sec in', width / 2, height / 2);
* beginning = false;
* } else {
* // here we jump 2 seconds into the sound
* ele.play().time(2);
* background(250);
* text('start at beginning', width / 2, height / 2);
* beginning = true;
* }
* }
*
*/
/**
* @method time
* @param {Number} time time to jump to (in seconds)
* @chainable
*/
p5.MediaElement.prototype.time = function(val) {
if (typeof val === 'undefined') {
return this.elt.currentTime;
} else {
this.elt.currentTime = val;
return this;
}
};
/**
* Returns the duration of the HTML5 media element.
*
* @method duration
* @return {Number} duration
*
* @example
*
* var ele;
* function setup() {
* //p5.MediaElement objects are usually created
* //by calling the createAudio(), createVideo(),
* //and createCapture() functions.
* //In this example we create
* //a new p5.MediaElement via createAudio().
* ele = createAudio('assets/doorbell.mp3');
* background(250);
* textAlign(CENTER);
* text('Click to know the duration!', 10, 25, 70, 80);
* }
* function mouseClicked() {
* ele.play();
* background(200);
* //ele.duration dislpays the duration
* text(ele.duration() + ' seconds', width / 2, height / 2);
* }
*
*/
p5.MediaElement.prototype.duration = function() {
return this.elt.duration;
};
p5.MediaElement.prototype.pixels = [];
p5.MediaElement.prototype._ensureCanvas = function() {
if (!this.canvas) {
this.canvas = document.createElement('canvas');
this.drawingContext = this.canvas.getContext('2d');
this.setModified(true);
}
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._pixelsDirty = true;
}
var currentTime = this.elt.currentTime;
if (this._pixelsDirty || this._pixelsTime !== currentTime) {
// only update the pixels array if it's dirty, or
// if the video time has changed.
this._pixelsTime = currentTime;
this._pixelsDirty = true;
this.drawingContext.drawImage(
this.elt,
0,
0,
this.canvas.width,
this.canvas.height
);
this.setModified(true);
}
}
};
p5.MediaElement.prototype.loadPixels = function() {
this._ensureCanvas();
return p5.Renderer2D.prototype.loadPixels.apply(this, arguments);
};
p5.MediaElement.prototype.updatePixels = function(x, y, w, h) {
if (this.loadedmetadata) {
// wait for metadata
this._ensureCanvas();
p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
}
this.setModified(true);
return this;
};
p5.MediaElement.prototype.get = function() {
this._ensureCanvas();
return p5.Renderer2D.prototype.get.apply(this, arguments);
};
p5.MediaElement.prototype._getPixel = function() {
this.loadPixels();
return p5.Renderer2D.prototype._getPixel.apply(this, arguments);
};
p5.MediaElement.prototype.set = function(x, y, imgOrCol) {
if (this.loadedmetadata) {
// wait for metadata
this._ensureCanvas();
p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
this.setModified(true);
}
};
p5.MediaElement.prototype.copy = function() {
this._ensureCanvas();
p5.Renderer2D.prototype.copy.apply(this, arguments);
};
p5.MediaElement.prototype.mask = function() {
this.loadPixels();
this.setModified(true);
p5.Image.prototype.mask.apply(this, arguments);
};
/**
* helper method for web GL mode to figure out if the element
* has been modified and might need to be re-uploaded to texture
* memory between frames.
* @method isModified
* @private
* @return {boolean} a boolean indicating whether or not the
* image has been updated or modified since last texture upload.
*/
p5.MediaElement.prototype.isModified = function() {
return this._modified;
};
/**
* helper method for web GL mode to indicate that an element has been
* changed or unchanged since last upload. gl texture upload will
* set this value to false after uploading the texture; or might set
* it to true if metadata has become available but there is no actual
* texture data available yet..
* @method setModified
* @param {boolean} val sets whether or not the element has been
* modified.
* @private
*/
p5.MediaElement.prototype.setModified = function(value) {
this._modified = value;
};
/**
* 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.
* @chainable
* @example
*
* function setup() {
* var audioEl = createAudio('assets/beat.mp3');
* audioEl.showControls();
* 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|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);
}
} else {
// otherwise connect to master output of p5.sound / AudioContext
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
* @example
*
* var ele;
* function setup() {
* //p5.MediaElement objects are usually created
* //by calling the createAudio(), createVideo(),
* //and createCapture() functions.
* //In this example we create
* //a new p5.MediaElement via createAudio()
* ele = createAudio('assets/lucky_dragons.mp3');
* background(200);
* textAlign(CENTER);
* text('Click to Show Controls!', 10, 25, 70, 80);
* }
* function mousePressed() {
* ele.showControls();
* background(200);
* text('Controls Shown', width / 2, height / 2);
* }
*
*/
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
* @example
*
* var ele;
* function setup() {
* //p5.MediaElement objects are usually created
* //by calling the createAudio(), createVideo(),
* //and createCapture() functions.
* //In this example we create
* //a new p5.MediaElement via createAudio()
* ele = createAudio('assets/lucky_dragons.mp3');
* ele.showControls();
* background(200);
* textAlign(CENTER);
* text('Click to hide Controls!', 10, 25, 70, 80);
* }
* function mousePressed() {
* ele.hideControls();
* background(200);
* text('Controls hidden', width / 2, height / 2);
* }
*
*/
p5.MediaElement.prototype.hideControls = function() {
this.elt.controls = false;
};
/*** SCHEDULE EVENTS ***/
// 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;
};
/**
* 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() {
* noCanvas();
*
* var 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
* @example
*
* var audioEl, id1, id2;
* function setup() {
* background(255, 255, 255);
* audioEl = createAudio('assets/beat.mp3');
* audioEl.showControls();
* // schedule five calls to changeBackground
* id1 = 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));
* id2 = audioEl.addCue(4.2, changeBackground, color(255, 255, 0));
* text('Click to remove first and last Cue!', 10, 25, 70, 80);
* }
* function mousePressed() {
* audioEl.removeCue(id1);
* audioEl.removeCue(id2);
* }
* function changeBackground(val) {
* background(val);
* }
*
*/
p5.MediaElement.prototype.removeCue = function(id) {
for (var i = 0; i < this._cues.length; i++) {
if (this._cues[i].id === id) {
console.log(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
* @param {Number} id ID of the cue, as returned by addCue
* @example
*
* var audioEl;
* function setup() {
* background(255, 255, 255);
* audioEl = createAudio('assets/beat.mp3');
* //Show the default MediaElement controls, as determined by the web browser
* audioEl.showControls();
* // schedule calls to changeBackground
* background(200);
* text('Click to change Cue!', 10, 25, 70, 80);
* 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));
* }
* function mousePressed() {
* // here we clear the scheduled callbacks
* audioEl.clearCues();
* // then we add some more callbacks
* audioEl.addCue(1, changeBackground, color(2, 2, 2));
* audioEl.addCue(3, changeBackground, color(255, 255, 0));
* }
* function changeBackground(val) {
* background(val);
* }
*
*/
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;
};
/**
* Base class for a file.
* Used for Element.drop and createFileInput.
*
* @class p5.File
* @constructor
* @param {File} file File that is wrapped
*/
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;
};
p5.File._createLoader = function(theFile, callback) {
var reader = new FileReader();
reader.onload = function(e) {
var p5file = new p5.File(theFile);
p5file.data = e.target.result;
callback(p5file);
};
return reader;
};
p5.File._load = function(f, callback) {
// Text or data?
// This should likely be improved
if (/^text\//.test(f.type)) {
p5.File._createLoader(f, callback).readAsText(f);
} else if (!/^(video|audio)\//.test(f.type)) {
p5.File._createLoader(f, callback).readAsDataURL(f);
} else {
var file = new p5.File(f);
file.data = URL.createObjectURL(f);
callback(file);
}
};
});