├── index.html ├── bullet.js ├── README.md ├── main.js └── p5.dom.js /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 |The web is much more than just canvas and p5.dom makes it easy to interact 4 | * with other HTML5 objects, including text, hyperlink, image, input, video, 5 | * audio, and webcam.
6 | *There is a set of creation methods, DOM manipulation methods, and 7 | * an extended p5.Element that supports a range of HTML elements. See the 8 | * 9 | * beyond the canvas tutorial for a full overview of how this addon works. 10 | * 11 | *
Methods and properties shown in black are part of the p5.js core, items in 12 | * blue are part of the p5.dom library. You will need to include an extra file 13 | * in order to access the blue functions. See the 14 | * using a library 15 | * section for information on how to include this library. p5.dom comes with 16 | * p5 complete or you can download the single file 17 | * 18 | * here.
19 | *See tutorial: beyond the canvas 20 | * for more info on how to use this library. 21 | * 22 | * @module p5.dom 23 | * @submodule p5.dom 24 | * @for p5 25 | * @main 26 | */ 27 | 28 | (function(root, factory) { 29 | if (typeof define === 'function' && define.amd) 30 | define('p5.dom', ['p5'], function(p5) { 31 | factory(p5); 32 | }); 33 | else if (typeof exports === 'object') factory(require('../p5')); 34 | else factory(root['p5']); 35 | })(this, function(p5) { 36 | // ============================================================================= 37 | // p5 additions 38 | // ============================================================================= 39 | 40 | /** 41 | * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' 42 | * prefixes to specify an ID or class respectively, and none for a tag) and returns it as 43 | * a p5.Element. If a class or tag name is given with more than 1 element, 44 | * only the first element will be returned. 45 | * The DOM node itself can be accessed with .elt. 46 | * Returns null if none found. You can also specify a container to search within. 47 | * 48 | * @method select 49 | * @param {String} name id, class, or tag name of element to search for 50 | * @param {String|p5.Element|HTMLElement} [container] id, p5.Element, or 51 | * HTML element to search within 52 | * @return {p5.Element|null} p5.Element containing node found 53 | * @example 54 | *
55 | * function setup() {
56 | * createCanvas(100, 100);
57 | * //translates canvas 50px down
58 | * select('canvas').position(100, 100);
59 | * }
60 | *
62 | * // these are all valid calls to select()
63 | * var a = select('#moo');
64 | * var b = select('#blah', '#myContainer');
65 | * var c, e;
66 | * if (b) {
67 | * c = select('#foo', b);
68 | * }
69 | * var d = document.getElementById('beep');
70 | * if (d) {
71 | * e = select('p', d);
72 | * }
73 | * [a, b, c, d, e]; // unused
74 | *
121 | * function setup() {
122 | * createButton('btn');
123 | * createButton('2nd btn');
124 | * createButton('3rd btn');
125 | * var buttons = selectAll('button');
126 | *
127 | * for (var i = 0; i < buttons.length; i++) {
128 | * buttons[i].size(100, 100);
129 | * }
130 | * }
131 | *
133 | * // these are all valid calls to selectAll()
134 | * var a = selectAll('.moo');
135 | * a = selectAll('div');
136 | * a = selectAll('button', '#myContainer');
137 | *
138 | * var d = select('#container');
139 | * a = selectAll('p', d);
140 | *
141 | * var f = document.getElementById('beep');
142 | * a = select('.blah', f);
143 | *
144 | * a; // unused
145 | *
225 | * function setup() {
226 | * createCanvas(100, 100);
227 | * createDiv('this is some text');
228 | * createP('this is a paragraph');
229 | * }
230 | * function mousePressed() {
231 | * removeElements(); // this will remove the div and p, not canvas
232 | * }
233 | *
258 | * var sel;
259 | *
260 | * function setup() {
261 | * textAlign(CENTER);
262 | * background(200);
263 | * sel = createSelect();
264 | * sel.position(10, 10);
265 | * sel.option('pear');
266 | * sel.option('kiwi');
267 | * sel.option('grape');
268 | * sel.changed(mySelectEvent);
269 | * }
270 | *
271 | * function mySelectEvent() {
272 | * var item = sel.value();
273 | * background(200);
274 | * text("it's a " + item + '!', 50, 50);
275 | * }
276 | *
279 | * var checkbox;
280 | * var cnv;
281 | *
282 | * function setup() {
283 | * checkbox = createCheckbox(' fill');
284 | * checkbox.changed(changeFill);
285 | * cnv = createCanvas(100, 100);
286 | * cnv.position(0, 30);
287 | * noFill();
288 | * }
289 | *
290 | * function draw() {
291 | * background(200);
292 | * ellipse(50, 50, 50, 50);
293 | * }
294 | *
295 | * function changeFill() {
296 | * if (checkbox.checked()) {
297 | * fill(0);
298 | * } else {
299 | * noFill();
300 | * }
301 | * }
302 | *
328 | * // Open your console to see the output
329 | * function setup() {
330 | * var inp = createInput('');
331 | * inp.input(myInputEvent);
332 | * }
333 | *
334 | * function myInputEvent() {
335 | * console.log('you are typing: ', this.value());
336 | * }
337 | *
371 | * createDiv('this is some text');
372 | *
386 | * createP('this is some text');
387 | *
400 | * createSpan('this is some text');
401 | *
426 | * createImg('http://p5js.org/img/asterisk-01.png');
427 | *
476 | * createA('http://p5js.org/', 'this is a link');
477 | *
504 | * var slider;
505 | * function setup() {
506 | * slider = createSlider(0, 255, 100);
507 | * slider.position(10, 10);
508 | * slider.style('width', '80px');
509 | * }
510 | *
511 | * function draw() {
512 | * var val = slider.value();
513 | * background(val);
514 | * }
515 | *
518 | * var slider;
519 | * function setup() {
520 | * colorMode(HSB);
521 | * slider = createSlider(0, 360, 60, 40);
522 | * slider.position(10, 10);
523 | * slider.style('width', '80px');
524 | * }
525 | *
526 | * function draw() {
527 | * var val = slider.value();
528 | * background(val, 100, 100, 1);
529 | * }
530 | *
560 | * var button;
561 | * function setup() {
562 | * createCanvas(100, 100);
563 | * background(0);
564 | * button = createButton('click me');
565 | * button.position(19, 19);
566 | * button.mousePressed(changeBG);
567 | * }
568 | *
569 | * function changeBG() {
570 | * var val = random(255);
571 | * background(val);
572 | * }
573 | *
593 | * var checkbox;
594 | *
595 | * function setup() {
596 | * checkbox = createCheckbox('label', false);
597 | * checkbox.changed(myCheckedEvent);
598 | * }
599 | *
600 | * function myCheckedEvent() {
601 | * if (this.checked()) {
602 | * console.log('Checking!');
603 | * } else {
604 | * console.log('Unchecking!');
605 | * }
606 | * }
607 | *
659 | * var sel;
660 | *
661 | * function setup() {
662 | * textAlign(CENTER);
663 | * background(200);
664 | * sel = createSelect();
665 | * sel.position(10, 10);
666 | * sel.option('pear');
667 | * sel.option('kiwi');
668 | * sel.option('grape');
669 | * sel.changed(mySelectEvent);
670 | * }
671 | *
672 | * function mySelectEvent() {
673 | * var item = sel.value();
674 | * background(200);
675 | * text('It is a ' + item + '!', 50, 50);
676 | * }
677 | *
765 | * var radio;
766 | *
767 | * function setup() {
768 | * radio = createRadio();
769 | * radio.option('black');
770 | * radio.option('white');
771 | * radio.option('gray');
772 | * radio.style('width', '60px');
773 | * textAlign(CENTER);
774 | * fill(255, 0, 0);
775 | * }
776 | *
777 | * function draw() {
778 | * var val = radio.value();
779 | * background(val);
780 | * text(val, width / 2, height / 2);
781 | * }
782 | *
784 | * var radio;
785 | *
786 | * function setup() {
787 | * radio = createRadio();
788 | * radio.option('apple', 1);
789 | * radio.option('bread', 2);
790 | * radio.option('juice', 3);
791 | * radio.style('width', '60px');
792 | * textAlign(CENTER);
793 | * }
794 | *
795 | * function draw() {
796 | * background(200);
797 | * var val = radio.value();
798 | * if (val) {
799 | * text('item cost is $' + val, width / 2, height / 2);
800 | * }
801 | * }
802 | *
904 | * var inp1, inp2;
905 | * function setup() {
906 | * createCanvas(100, 100);
907 | * background('grey');
908 | * inp1 = createColorPicker('#ff0000');
909 | * inp2 = createColorPicker(color('yellow'));
910 | * inp1.input(setShade1);
911 | * inp2.input(setShade2);
912 | * setMidShade();
913 | * }
914 | *
915 | * function setMidShade() {
916 | * // Finding a shade between the two
917 | * var commonShade = lerpColor(inp1.color(), inp2.color(), 0.5);
918 | * fill(commonShade);
919 | * rect(20, 20, 60, 60);
920 | * }
921 | *
922 | * function setShade1() {
923 | * setMidShade();
924 | * console.log('You are choosing shade 1 to be : ', this.value());
925 | * }
926 | * function setShade2() {
927 | * setMidShade();
928 | * console.log('You are choosing shade 2 to be : ', this.value());
929 | * }
930 | *
931 | *
979 | * function setup() {
980 | * var inp = createInput('');
981 | * inp.input(myInputEvent);
982 | * }
983 | *
984 | * function myInputEvent() {
985 | * console.log('you are typing: ', this.value());
986 | * }
987 | *
1007 | * let input;
1008 | * let img;
1009 | *
1010 | * function setup() {
1011 | * input = createFileInput(handleFile);
1012 | * input.position(0, 0);
1013 | * }
1014 | *
1015 | * function draw() {
1016 | * background(255);
1017 | * if (img) {
1018 | * image(img, 0, 0, width, height);
1019 | * }
1020 | * }
1021 | *
1022 | * function handleFile(file) {
1023 | * print(file);
1024 | * if (file.type === 'image') {
1025 | * img = createImg(file.data);
1026 | * img.hide();
1027 | * } else {
1028 | * img = null;
1029 | * }
1030 | * }
1031 | *
1135 | * var vid;
1136 | * function setup() {
1137 | * noCanvas();
1138 | *
1139 | * vid = createVideo(
1140 | * ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm'],
1141 | * vidLoad
1142 | * );
1143 | *
1144 | * vid.size(100, 100);
1145 | * }
1146 | *
1147 | * // This function is called when the video loads
1148 | * function vidLoad() {
1149 | * vid.loop();
1150 | * vid.volume(0);
1151 | * }
1152 | *
1183 | * var ele;
1184 | * function setup() {
1185 | * ele = createAudio('assets/beat.mp3');
1186 | *
1187 | * // here we set the element to autoplay
1188 | * // The element will play as soon
1189 | * // as it is able to do so.
1190 | * ele.autoplay(true);
1191 | * }
1192 | * Creates a new HTML5 <video> element that contains the audio/video 1246 | * feed from a webcam. The element is separate from the canvas and is 1247 | * displayed by default. The element can be hidden using .hide(). The feed 1248 | * can be drawn onto the canvas using image(). The loadedmetadata property can 1249 | * be used to detect when the element has fully loaded (see second example).
1250 | *More specific properties of the feed can be passing in a Constraints object. 1251 | * See the 1252 | * W3C 1253 | * spec for possible properties. Note that not all of these are supported 1254 | * by all browsers.
1255 | *Security note: A new browser security specification requires that getUserMedia, 1256 | * which is behind createCapture(), only works when you're running the code locally, 1257 | * or on HTTPS. Learn more here 1258 | * and here.
1259 | * 1260 | * @method createCapture 1261 | * @param {String|Constant|Object} type type of capture, either VIDEO or 1262 | * AUDIO if none specified, default both, 1263 | * or a Constraints object 1264 | * @param {Function} [callback] function to be called once 1265 | * stream has loaded 1266 | * @return {p5.Element} capture video p5.Element 1267 | * @example 1268 | *
1269 | * var capture;
1270 | *
1271 | * function setup() {
1272 | * createCanvas(480, 480);
1273 | * capture = createCapture(VIDEO);
1274 | * capture.hide();
1275 | * }
1276 | *
1277 | * function draw() {
1278 | * image(capture, 0, 0, width, width * capture.height / capture.width);
1279 | * filter(INVERT);
1280 | * }
1281 | *
1283 | * function setup() {
1284 | * createCanvas(480, 120);
1285 | * var constraints = {
1286 | * video: {
1287 | * mandatory: {
1288 | * minWidth: 1280,
1289 | * minHeight: 720
1290 | * },
1291 | * optional: [{ maxFrameRate: 10 }]
1292 | * },
1293 | * audio: true
1294 | * };
1295 | * createCapture(constraints, function(stream) {
1296 | * console.log(stream);
1297 | * });
1298 | * }
1299 | *
1301 | * var capture;
1302 | *
1303 | * function setup() {
1304 | * createCanvas(640, 480);
1305 | * capture = createCapture(VIDEO);
1306 | * }
1307 | * function draw() {
1308 | * background(0);
1309 | * if (capture.loadedmetadata) {
1310 | * var c = capture.get(0, 0, 100, 100);
1311 | * image(c, 0, 0);
1312 | * }
1313 | * }
1314 | *
1315 | */
1316 | p5.prototype.createCapture = function() {
1317 | p5._validateParameters('createCapture', arguments);
1318 | var useVideo = true;
1319 | var useAudio = true;
1320 | var constraints;
1321 | var cb;
1322 | for (var i = 0; i < arguments.length; i++) {
1323 | if (arguments[i] === p5.prototype.VIDEO) {
1324 | useAudio = false;
1325 | } else if (arguments[i] === p5.prototype.AUDIO) {
1326 | useVideo = false;
1327 | } else if (typeof arguments[i] === 'object') {
1328 | constraints = arguments[i];
1329 | } else if (typeof arguments[i] === 'function') {
1330 | cb = arguments[i];
1331 | }
1332 | }
1333 | if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
1334 | var elt = document.createElement('video');
1335 | // required to work in iOS 11 & up:
1336 | elt.setAttribute('playsinline', '');
1337 |
1338 | if (!constraints) {
1339 | constraints = { video: useVideo, audio: useAudio };
1340 | }
1341 |
1342 | navigator.mediaDevices.getUserMedia(constraints).then(
1343 | function(stream) {
1344 | try {
1345 | if ('srcObject' in elt) {
1346 | elt.srcObject = stream;
1347 | } else {
1348 | elt.src = window.URL.createObjectURL(stream);
1349 | }
1350 | } catch (err) {
1351 | elt.src = stream;
1352 | }
1353 | },
1354 | function(e) {
1355 | console.log(e);
1356 | }
1357 | );
1358 | } else {
1359 | throw 'getUserMedia not supported in this browser';
1360 | }
1361 | var c = addElement(elt, this, true);
1362 | c.loadedmetadata = false;
1363 | // set width and height onload metadata
1364 | elt.addEventListener('loadedmetadata', function() {
1365 | elt.play();
1366 | if (elt.width) {
1367 | c.width = elt.videoWidth = elt.width;
1368 | c.height = elt.videoHeight = elt.height;
1369 | } else {
1370 | c.width = c.elt.width = elt.videoWidth;
1371 | c.height = c.elt.height = elt.videoHeight;
1372 | }
1373 | c.loadedmetadata = true;
1374 | if (cb) {
1375 | cb(elt.srcObject);
1376 | }
1377 | });
1378 | return c;
1379 | };
1380 |
1381 | /**
1382 | * Creates element with given tag in the DOM with given content.
1383 | * Appends to the container node if one is specified, otherwise
1384 | * appends to body.
1385 | *
1386 | * @method createElement
1387 | * @param {String} tag tag for the new element
1388 | * @param {String} [content] html content to be inserted into the element
1389 | * @return {p5.Element} pointer to p5.Element holding created node
1390 | * @example
1391 | *
1392 | * createElement('h2', 'im an h2 p5.element!');
1393 | *
1394 | */
1395 | p5.prototype.createElement = function(tag, content) {
1396 | p5._validateParameters('createElement', arguments);
1397 | var elt = document.createElement(tag);
1398 | if (typeof content !== 'undefined') {
1399 | elt.innerHTML = content;
1400 | }
1401 | return addElement(elt, this);
1402 | };
1403 |
1404 | // =============================================================================
1405 | // p5.Element additions
1406 | // =============================================================================
1407 | /**
1408 | *
1409 | * Adds specified class to the element.
1410 | *
1411 | * @for p5.Element
1412 | * @method addClass
1413 | * @param {String} class name of class to add
1414 | * @chainable
1415 | * @example
1416 | *
1417 | * var div = createDiv('div');
1418 | * div.addClass('myClass');
1419 | *
1420 | */
1421 | p5.Element.prototype.addClass = function(c) {
1422 | if (this.elt.className) {
1423 | if (!this.hasClass(c)) {
1424 | this.elt.className = this.elt.className + ' ' + c;
1425 | }
1426 | } else {
1427 | this.elt.className = c;
1428 | }
1429 | return this;
1430 | };
1431 |
1432 | /**
1433 | *
1434 | * Removes specified class from the element.
1435 | *
1436 | * @method removeClass
1437 | * @param {String} class name of class to remove
1438 | * @chainable
1439 | * @example
1440 | *
1441 | * // In this example, a class is set when the div is created
1442 | * // and removed when mouse is pressed. This could link up
1443 | * // with a CSS style rule to toggle style properties.
1444 | *
1445 | * var div;
1446 | *
1447 | * function setup() {
1448 | * div = createDiv('div');
1449 | * div.addClass('myClass');
1450 | * }
1451 | *
1452 | * function mousePressed() {
1453 | * div.removeClass('myClass');
1454 | * }
1455 | *
1456 | */
1457 | p5.Element.prototype.removeClass = function(c) {
1458 | // Note: Removing a class that does not exist does NOT throw an error in classList.remove method
1459 | this.elt.classList.remove(c);
1460 | return this;
1461 | };
1462 |
1463 | /**
1464 | *
1465 | * Checks if specified class already set to element
1466 | *
1467 | * @method hasClass
1468 | * @returns {boolean} a boolean value if element has specified class
1469 | * @param c {String} class name of class to check
1470 | * @example
1471 | *
1472 | * var div;
1473 | *
1474 | * function setup() {
1475 | * div = createDiv('div');
1476 | * div.addClass('show');
1477 | * }
1478 | *
1479 | * function mousePressed() {
1480 | * if (div.hasClass('show')) {
1481 | * div.addClass('show');
1482 | * } else {
1483 | * div.removeClass('show');
1484 | * }
1485 | * }
1486 | *
1487 | */
1488 | p5.Element.prototype.hasClass = function(c) {
1489 | return this.elt.classList.contains(c);
1490 | };
1491 |
1492 | /**
1493 | *
1494 | * Toggles element class
1495 | *
1496 | * @method toggleClass
1497 | * @param c {String} class name to toggle
1498 | * @chainable
1499 | * @example
1500 | *
1501 | * var div;
1502 | *
1503 | * function setup() {
1504 | * div = createDiv('div');
1505 | * div.addClass('show');
1506 | * }
1507 | *
1508 | * function mousePressed() {
1509 | * div.toggleClass('show');
1510 | * }
1511 | *
1512 | */
1513 | p5.Element.prototype.toggleClass = function(c) {
1514 | // classList also has a toggle() method, but we cannot use that yet as support is unclear.
1515 | // See https://github.com/processing/p5.js/issues/3631
1516 | // this.elt.classList.toggle(c);
1517 | if (this.elt.classList.contains(c)) {
1518 | this.elt.classList.remove(c);
1519 | } else {
1520 | this.elt.classList.add(c);
1521 | }
1522 | return this;
1523 | };
1524 |
1525 | /**
1526 | *
1527 | * Attaches the element as a child to the parent specified.
1528 | * Accepts either a string ID, DOM node, or p5.Element.
1529 | * If no argument is specified, an array of children DOM nodes is returned.
1530 | *
1531 | * @method child
1532 | * @returns {Node[]} an array of child nodes
1533 | * @example
1534 | *
1535 | * var div0 = createDiv('this is the parent');
1536 | * var div1 = createDiv('this is the child');
1537 | * div0.child(div1); // use p5.Element
1538 | *
1539 | *
1540 | * var div0 = createDiv('this is the parent');
1541 | * var div1 = createDiv('this is the child');
1542 | * div1.id('apples');
1543 | * div0.child('apples'); // use id
1544 | *
1545 | *
1546 | * // this example assumes there is a div already on the page
1547 | * // with id "myChildDiv"
1548 | * var div0 = createDiv('this is the parent');
1549 | * var elt = document.getElementById('myChildDiv');
1550 | * div0.child(elt); // use element from page
1551 | *
1552 | */
1553 | /**
1554 | * @method child
1555 | * @param {String|p5.Element} [child] the ID, DOM node, or p5.Element
1556 | * to add to the current element
1557 | * @chainable
1558 | */
1559 | p5.Element.prototype.child = function(c) {
1560 | if (typeof c === 'undefined') {
1561 | return this.elt.childNodes;
1562 | }
1563 | if (typeof c === 'string') {
1564 | if (c[0] === '#') {
1565 | c = c.substring(1);
1566 | }
1567 | c = document.getElementById(c);
1568 | } else if (c instanceof p5.Element) {
1569 | c = c.elt;
1570 | }
1571 | this.elt.appendChild(c);
1572 | return this;
1573 | };
1574 |
1575 | /**
1576 | * Centers a p5 Element either vertically, horizontally,
1577 | * or both, relative to its parent or according to
1578 | * the body if the Element has no parent. If no argument is passed
1579 | * the Element is aligned both vertically and horizontally.
1580 | *
1581 | * @method center
1582 | * @param {String} [align] passing 'vertical', 'horizontal' aligns element accordingly
1583 | * @chainable
1584 | *
1585 | * @example
1586 | *
1587 | * function setup() {
1588 | * var div = createDiv('').size(10, 10);
1589 | * div.style('background-color', 'orange');
1590 | * div.center();
1591 | * }
1592 | *
1593 | */
1594 | p5.Element.prototype.center = function(align) {
1595 | var style = this.elt.style.display;
1596 | var hidden = this.elt.style.display === 'none';
1597 | var parentHidden = this.parent().style.display === 'none';
1598 | var pos = { x: this.elt.offsetLeft, y: this.elt.offsetTop };
1599 |
1600 | if (hidden) this.show();
1601 |
1602 | this.elt.style.display = 'block';
1603 | this.position(0, 0);
1604 |
1605 | if (parentHidden) this.parent().style.display = 'block';
1606 |
1607 | var wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
1608 | var hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
1609 | var y = pos.y;
1610 | var x = pos.x;
1611 |
1612 | if (align === 'both' || align === undefined) {
1613 | this.position(wOffset / 2, hOffset / 2);
1614 | } else if (align === 'horizontal') {
1615 | this.position(wOffset / 2, y);
1616 | } else if (align === 'vertical') {
1617 | this.position(x, hOffset / 2);
1618 | }
1619 |
1620 | this.style('display', style);
1621 |
1622 | if (hidden) this.hide();
1623 |
1624 | if (parentHidden) this.parent().style.display = 'none';
1625 |
1626 | return this;
1627 | };
1628 |
1629 | /**
1630 | *
1631 | * If an argument is given, sets the inner HTML of the element,
1632 | * replacing any existing html. If true is included as a second
1633 | * argument, html is appended instead of replacing existing html.
1634 | * If no arguments are given, returns
1635 | * the inner HTML of the element.
1636 | *
1637 | * @for p5.Element
1638 | * @method html
1639 | * @returns {String} the inner HTML of the element
1640 | * @example
1641 | *
1642 | * var div = createDiv('').size(100, 100);
1643 | * div.html('hi');
1644 | *
1645 | *
1646 | * var div = createDiv('Hello ').size(100, 100);
1647 | * div.html('World', true);
1648 | *
1649 | */
1650 | /**
1651 | * @method html
1652 | * @param {String} [html] the HTML to be placed inside the element
1653 | * @param {boolean} [append] whether to append HTML to existing
1654 | * @chainable
1655 | */
1656 | p5.Element.prototype.html = function() {
1657 | if (arguments.length === 0) {
1658 | return this.elt.innerHTML;
1659 | } else if (arguments[1]) {
1660 | this.elt.innerHTML += arguments[0];
1661 | return this;
1662 | } else {
1663 | this.elt.innerHTML = arguments[0];
1664 | return this;
1665 | }
1666 | };
1667 |
1668 | /**
1669 | *
1670 | * Sets the position of the element relative to (0, 0) of the
1671 | * window. Essentially, sets position:absolute and left and top
1672 | * properties of style. If no arguments given returns the x and y position
1673 | * of the element in an object.
1674 | *
1675 | * @method position
1676 | * @returns {Object} the x and y position of the element in an object
1677 | * @example
1678 | *
1679 | * function setup() {
1680 | * var cnv = createCanvas(100, 100);
1681 | * // positions canvas 50px to the right and 100px
1682 | * // below upper left corner of the window
1683 | * cnv.position(50, 100);
1684 | * }
1685 | *
1686 | */
1687 | /**
1688 | * @method position
1689 | * @param {Number} [x] x-position relative to upper left of window
1690 | * @param {Number} [y] y-position relative to upper left of window
1691 | * @chainable
1692 | */
1693 | p5.Element.prototype.position = function() {
1694 | if (arguments.length === 0) {
1695 | return { x: this.elt.offsetLeft, y: this.elt.offsetTop };
1696 | } else {
1697 | this.elt.style.position = 'absolute';
1698 | this.elt.style.left = arguments[0] + 'px';
1699 | this.elt.style.top = arguments[1] + 'px';
1700 | this.x = arguments[0];
1701 | this.y = arguments[1];
1702 | return this;
1703 | }
1704 | };
1705 |
1706 | /* Helper method called by p5.Element.style() */
1707 | p5.Element.prototype._translate = function() {
1708 | this.elt.style.position = 'absolute';
1709 | // save out initial non-translate transform styling
1710 | var transform = '';
1711 | if (this.elt.style.transform) {
1712 | transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
1713 | transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
1714 | }
1715 | if (arguments.length === 2) {
1716 | this.elt.style.transform =
1717 | 'translate(' + arguments[0] + 'px, ' + arguments[1] + 'px)';
1718 | } else if (arguments.length > 2) {
1719 | this.elt.style.transform =
1720 | 'translate3d(' +
1721 | arguments[0] +
1722 | 'px,' +
1723 | arguments[1] +
1724 | 'px,' +
1725 | arguments[2] +
1726 | 'px)';
1727 | if (arguments.length === 3) {
1728 | this.elt.parentElement.style.perspective = '1000px';
1729 | } else {
1730 | this.elt.parentElement.style.perspective = arguments[3] + 'px';
1731 | }
1732 | }
1733 | // add any extra transform styling back on end
1734 | this.elt.style.transform += transform;
1735 | return this;
1736 | };
1737 |
1738 | /* Helper method called by p5.Element.style() */
1739 | p5.Element.prototype._rotate = function() {
1740 | // save out initial non-rotate transform styling
1741 | var transform = '';
1742 | if (this.elt.style.transform) {
1743 | transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
1744 | transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
1745 | }
1746 |
1747 | if (arguments.length === 1) {
1748 | this.elt.style.transform = 'rotate(' + arguments[0] + 'deg)';
1749 | } else if (arguments.length === 2) {
1750 | this.elt.style.transform =
1751 | 'rotate(' + arguments[0] + 'deg, ' + arguments[1] + 'deg)';
1752 | } else if (arguments.length === 3) {
1753 | this.elt.style.transform = 'rotateX(' + arguments[0] + 'deg)';
1754 | this.elt.style.transform += 'rotateY(' + arguments[1] + 'deg)';
1755 | this.elt.style.transform += 'rotateZ(' + arguments[2] + 'deg)';
1756 | }
1757 | // add remaining transform back on
1758 | this.elt.style.transform += transform;
1759 | return this;
1760 | };
1761 |
1762 | /**
1763 | * Sets the given style (css) property (1st arg) of the element with the
1764 | * given value (2nd arg). If a single argument is given, .style()
1765 | * returns the value of the given property; however, if the single argument
1766 | * is given in css syntax ('text-align:center'), .style() sets the css
1767 | * appropriately.
1768 | *
1769 | * @method style
1770 | * @param {String} property property to be set
1771 | * @returns {String} value of property
1772 | * @example
1773 | *
1774 | * var myDiv = createDiv('I like pandas.');
1775 | * myDiv.style('font-size', '18px');
1776 | * myDiv.style('color', '#ff0000');
1777 | *
1778 | *
1779 | * var col = color(25, 23, 200, 50);
1780 | * var button = createButton('button');
1781 | * button.style('background-color', col);
1782 | * button.position(10, 10);
1783 | *
1784 | *
1785 | * var myDiv;
1786 | * function setup() {
1787 | * background(200);
1788 | * myDiv = createDiv('I like gray.');
1789 | * myDiv.position(20, 20);
1790 | * }
1791 | *
1792 | * function draw() {
1793 | * myDiv.style('font-size', mouseX + 'px');
1794 | * }
1795 | *
1796 | */
1797 | /**
1798 | * @method style
1799 | * @param {String} property
1800 | * @param {String|Number|p5.Color} value value to assign to property
1801 | * @return {String} current value of property, if no value is given as second argument
1802 | * @chainable
1803 | */
1804 | p5.Element.prototype.style = function(prop, val) {
1805 | var self = this;
1806 |
1807 | if (val instanceof p5.Color) {
1808 | val =
1809 | 'rgba(' +
1810 | val.levels[0] +
1811 | ',' +
1812 | val.levels[1] +
1813 | ',' +
1814 | val.levels[2] +
1815 | ',' +
1816 | val.levels[3] / 255 +
1817 | ')';
1818 | }
1819 |
1820 | if (typeof val === 'undefined') {
1821 | // input provided as single line string
1822 | if (prop.indexOf(':') === -1) {
1823 | var styles = window.getComputedStyle(self.elt);
1824 | var style = styles.getPropertyValue(prop);
1825 | return style;
1826 | } else {
1827 | var attrs = prop.split(';');
1828 | for (var i = 0; i < attrs.length; i++) {
1829 | var parts = attrs[i].split(':');
1830 | if (parts[0] && parts[1]) {
1831 | this.elt.style[parts[0].trim()] = parts[1].trim();
1832 | }
1833 | }
1834 | }
1835 | } else {
1836 | // input provided as key,val pair
1837 | this.elt.style[prop] = val;
1838 | if (
1839 | prop === 'width' ||
1840 | prop === 'height' ||
1841 | prop === 'left' ||
1842 | prop === 'top'
1843 | ) {
1844 | var numVal = val.replace(/\D+/g, '');
1845 | this[prop] = parseInt(numVal, 10);
1846 | }
1847 | }
1848 | return this;
1849 | };
1850 |
1851 | /**
1852 | *
1853 | * Adds a new attribute or changes the value of an existing attribute
1854 | * on the specified element. If no value is specified, returns the
1855 | * value of the given attribute, or null if attribute is not set.
1856 | *
1857 | * @method attribute
1858 | * @return {String} value of attribute
1859 | *
1860 | * @example
1861 | *
1862 | * var myDiv = createDiv('I like pandas.');
1863 | * myDiv.attribute('align', 'center');
1864 | *
1865 | */
1866 | /**
1867 | * @method attribute
1868 | * @param {String} attr attribute to set
1869 | * @param {String} value value to assign to attribute
1870 | * @chainable
1871 | */
1872 | p5.Element.prototype.attribute = function(attr, value) {
1873 | //handling for checkboxes and radios to ensure options get
1874 | //attributes not divs
1875 | if (
1876 | this.elt.firstChild != null &&
1877 | (this.elt.firstChild.type === 'checkbox' ||
1878 | this.elt.firstChild.type === 'radio')
1879 | ) {
1880 | if (typeof value === 'undefined') {
1881 | return this.elt.firstChild.getAttribute(attr);
1882 | } else {
1883 | for (var i = 0; i < this.elt.childNodes.length; i++) {
1884 | this.elt.childNodes[i].setAttribute(attr, value);
1885 | }
1886 | }
1887 | } else if (typeof value === 'undefined') {
1888 | return this.elt.getAttribute(attr);
1889 | } else {
1890 | this.elt.setAttribute(attr, value);
1891 | return this;
1892 | }
1893 | };
1894 |
1895 | /**
1896 | *
1897 | * Removes an attribute on the specified element.
1898 | *
1899 | * @method removeAttribute
1900 | * @param {String} attr attribute to remove
1901 | * @chainable
1902 | *
1903 | * @example
1904 | *
1905 | * var button;
1906 | * var checkbox;
1907 | *
1908 | * function setup() {
1909 | * checkbox = createCheckbox('enable', true);
1910 | * checkbox.changed(enableButton);
1911 | * button = createButton('button');
1912 | * button.position(10, 10);
1913 | * }
1914 | *
1915 | * function enableButton() {
1916 | * if (this.checked()) {
1917 | * // Re-enable the button
1918 | * button.removeAttribute('disabled');
1919 | * } else {
1920 | * // Disable the button
1921 | * button.attribute('disabled', '');
1922 | * }
1923 | * }
1924 | *
1925 | */
1926 | p5.Element.prototype.removeAttribute = function(attr) {
1927 | if (
1928 | this.elt.firstChild != null &&
1929 | (this.elt.firstChild.type === 'checkbox' ||
1930 | this.elt.firstChild.type === 'radio')
1931 | ) {
1932 | for (var i = 0; i < this.elt.childNodes.length; i++) {
1933 | this.elt.childNodes[i].removeAttribute(attr);
1934 | }
1935 | }
1936 | this.elt.removeAttribute(attr);
1937 | return this;
1938 | };
1939 |
1940 | /**
1941 | * Either returns the value of the element if no arguments
1942 | * given, or sets the value of the element.
1943 | *
1944 | * @method value
1945 | * @return {String|Number} value of the element
1946 | * @example
1947 | *
1948 | * // gets the value
1949 | * var inp;
1950 | * function setup() {
1951 | * inp = createInput('');
1952 | * }
1953 | *
1954 | * function mousePressed() {
1955 | * print(inp.value());
1956 | * }
1957 | *
1958 | *
1959 | * // sets the value
1960 | * var inp;
1961 | * function setup() {
1962 | * inp = createInput('myValue');
1963 | * }
1964 | *
1965 | * function mousePressed() {
1966 | * inp.value('myValue');
1967 | * }
1968 | *
1969 | */
1970 | /**
1971 | * @method value
1972 | * @param {String|Number} value
1973 | * @chainable
1974 | */
1975 | p5.Element.prototype.value = function() {
1976 | if (arguments.length > 0) {
1977 | this.elt.value = arguments[0];
1978 | return this;
1979 | } else {
1980 | if (this.elt.type === 'range') {
1981 | return parseFloat(this.elt.value);
1982 | } else return this.elt.value;
1983 | }
1984 | };
1985 |
1986 | /**
1987 | *
1988 | * Shows the current element. Essentially, setting display:block for the style.
1989 | *
1990 | * @method show
1991 | * @chainable
1992 | * @example
1993 | *
1994 | * var div = createDiv('div');
1995 | * div.style('display', 'none');
1996 | * div.show(); // turns display to block
1997 | *
1998 | */
1999 | p5.Element.prototype.show = function() {
2000 | this.elt.style.display = 'block';
2001 | return this;
2002 | };
2003 |
2004 | /**
2005 | * Hides the current element. Essentially, setting display:none for the style.
2006 | *
2007 | * @method hide
2008 | * @chainable
2009 | * @example
2010 | *
2011 | * var div = createDiv('this is a div');
2012 | * div.hide();
2013 | *
2014 | */
2015 | p5.Element.prototype.hide = function() {
2016 | this.elt.style.display = 'none';
2017 | return this;
2018 | };
2019 |
2020 | /**
2021 | *
2022 | * Sets the width and height of the element. AUTO can be used to
2023 | * only adjust one dimension at a time. If no arguments are given, it
2024 | * returns the width and height of the element in an object. In case of
2025 | * elements which need to be loaded, such as images, it is recommended
2026 | * to call the function after the element has finished loading.
2027 | *
2028 | * @method size
2029 | * @return {Object} the width and height of the element in an object
2030 | * @example
2031 | *
2032 | * let div = createDiv('this is a div');
2033 | * div.size(100, 100);
2034 | * let img = createImg('assets/laDefense.jpg', () => {
2035 | * img.size(10, AUTO);
2036 | * });
2037 | *
2038 | */
2039 | /**
2040 | * @method size
2041 | * @param {Number|Constant} w width of the element, either AUTO, or a number
2042 | * @param {Number|Constant} [h] height of the element, either AUTO, or a number
2043 | * @chainable
2044 | */
2045 | p5.Element.prototype.size = function(w, h) {
2046 | if (arguments.length === 0) {
2047 | return { width: this.elt.offsetWidth, height: this.elt.offsetHeight };
2048 | } else {
2049 | var aW = w;
2050 | var aH = h;
2051 | var AUTO = p5.prototype.AUTO;
2052 | if (aW !== AUTO || aH !== AUTO) {
2053 | if (aW === AUTO) {
2054 | aW = h * this.width / this.height;
2055 | } else if (aH === AUTO) {
2056 | aH = w * this.height / this.width;
2057 | }
2058 | // set diff for cnv vs normal div
2059 | if (this.elt instanceof HTMLCanvasElement) {
2060 | var j = {};
2061 | var k = this.elt.getContext('2d');
2062 | var prop;
2063 | for (prop in k) {
2064 | j[prop] = k[prop];
2065 | }
2066 | this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
2067 | this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
2068 | this.elt.style.width = aW + 'px';
2069 | this.elt.style.height = aH + 'px';
2070 | this._pInst.scale(
2071 | this._pInst._pixelDensity,
2072 | this._pInst._pixelDensity
2073 | );
2074 | for (prop in j) {
2075 | this.elt.getContext('2d')[prop] = j[prop];
2076 | }
2077 | } else {
2078 | this.elt.style.width = aW + 'px';
2079 | this.elt.style.height = aH + 'px';
2080 | this.elt.width = aW;
2081 | this.elt.height = aH;
2082 | }
2083 |
2084 | this.width = this.elt.offsetWidth;
2085 | this.height = this.elt.offsetHeight;
2086 |
2087 | if (this._pInst && this._pInst._curElement) {
2088 | // main canvas associated with p5 instance
2089 | if (this._pInst._curElement.elt === this.elt) {
2090 | this._pInst._setProperty('width', this.elt.offsetWidth);
2091 | this._pInst._setProperty('height', this.elt.offsetHeight);
2092 | }
2093 | }
2094 | }
2095 | return this;
2096 | }
2097 | };
2098 |
2099 | /**
2100 | * Removes the element and deregisters all listeners.
2101 | * @method remove
2102 | * @example
2103 | *
2104 | * var myDiv = createDiv('this is some text');
2105 | * myDiv.remove();
2106 | *
2107 | */
2108 | p5.Element.prototype.remove = function() {
2109 | // deregister events
2110 | for (var ev in this._events) {
2111 | this.elt.removeEventListener(ev, this._events[ev]);
2112 | }
2113 | if (this.elt.parentNode) {
2114 | this.elt.parentNode.removeChild(this.elt);
2115 | }
2116 | delete this;
2117 | };
2118 |
2119 | /**
2120 | * Registers a callback that gets called every time a file that is
2121 | * dropped on the element has been loaded.
2122 | * p5 will load every dropped file into memory and pass it as a p5.File object to the callback.
2123 | * Multiple files dropped at the same time will result in multiple calls to the callback.
2124 | *
2125 | * You can optionally pass a second callback which will be registered to the raw
2126 | * drop event.
2127 | * The callback will thus be provided the original
2128 | * DragEvent.
2129 | * Dropping multiple files at the same time will trigger the second callback once per drop,
2130 | * whereas the first callback will trigger for each loaded file.
2131 | *
2132 | * @method drop
2133 | * @param {Function} callback callback to receive loaded file, called for each file dropped.
2134 | * @param {Function} [fxn] callback triggered once when files are dropped with the drop event.
2135 | * @chainable
2136 | * @example
2137 | *
2138 | * function setup() {
2139 | * var c = createCanvas(100, 100);
2140 | * background(200);
2141 | * textAlign(CENTER);
2142 | * text('drop file', width / 2, height / 2);
2143 | * c.drop(gotFile);
2144 | * }
2145 | *
2146 | * function gotFile(file) {
2147 | * background(200);
2148 | * text('received file:', width / 2, height / 2);
2149 | * text(file.name, width / 2, height / 2 + 50);
2150 | * }
2151 | *
2152 | *
2153 | *
2154 | * var img;
2155 | *
2156 | * function setup() {
2157 | * var c = createCanvas(100, 100);
2158 | * background(200);
2159 | * textAlign(CENTER);
2160 | * text('drop image', width / 2, height / 2);
2161 | * c.drop(gotFile);
2162 | * }
2163 | *
2164 | * function draw() {
2165 | * if (img) {
2166 | * image(img, 0, 0, width, height);
2167 | * }
2168 | * }
2169 | *
2170 | * function gotFile(file) {
2171 | * img = createImg(file.data).hide();
2172 | * }
2173 | *
2174 | *
2175 | * @alt
2176 | * Canvas turns into whatever image is dragged/dropped onto it.
2177 | */
2178 | p5.Element.prototype.drop = function(callback, fxn) {
2179 | // Is the file stuff supported?
2180 | if (window.File && window.FileReader && window.FileList && window.Blob) {
2181 | if (!this._dragDisabled) {
2182 | this._dragDisabled = true;
2183 |
2184 | var preventDefault = function(evt) {
2185 | evt.preventDefault();
2186 | };
2187 |
2188 | // If you want to be able to drop you've got to turn off
2189 | // a lot of default behavior.
2190 | // avoid `attachListener` here, since it overrides other handlers.
2191 | this.elt.addEventListener('dragover', preventDefault);
2192 |
2193 | // If this is a drag area we need to turn off the default behavior
2194 | this.elt.addEventListener('dragleave', preventDefault);
2195 | }
2196 |
2197 | // Deal with the files
2198 | p5.Element._attachListener(
2199 | 'drop',
2200 | function(evt) {
2201 | evt.preventDefault();
2202 | // Call the second argument as a callback that receives the raw drop event
2203 | if (typeof fxn === 'function') {
2204 | fxn.call(this, evt);
2205 | }
2206 | // A FileList
2207 | var files = evt.dataTransfer.files;
2208 |
2209 | // Load each one and trigger the callback
2210 | for (var i = 0; i < files.length; i++) {
2211 | var f = files[i];
2212 | p5.File._load(f, callback);
2213 | }
2214 | },
2215 | this
2216 | );
2217 | } else {
2218 | console.log('The File APIs are not fully supported in this browser.');
2219 | }
2220 |
2221 | return this;
2222 | };
2223 |
2224 | // =============================================================================
2225 | // p5.MediaElement additions
2226 | // =============================================================================
2227 |
2228 | /**
2229 | * Extends p5.Element to handle audio and video. In addition to the methods
2230 | * of p5.Element, it also contains methods for controlling media. It is not
2231 | * called directly, but p5.MediaElements are created by calling createVideo,
2232 | * createAudio, and createCapture.
2233 | *
2234 | * @class p5.MediaElement
2235 | * @constructor
2236 | * @param {String} elt DOM node that is wrapped
2237 | */
2238 | p5.MediaElement = function(elt, pInst) {
2239 | p5.Element.call(this, elt, pInst);
2240 |
2241 | var self = this;
2242 | this.elt.crossOrigin = 'anonymous';
2243 |
2244 | this._prevTime = 0;
2245 | this._cueIDCounter = 0;
2246 | this._cues = [];
2247 | this._pixelsState = this;
2248 | this._pixelDensity = 1;
2249 | this._modified = false;
2250 | this._pixelsDirty = true;
2251 | this._pixelsTime = -1; // the time at which we last updated 'pixels'
2252 |
2253 | /**
2254 | * Path to the media element source.
2255 | *
2256 | * @property src
2257 | * @return {String} src
2258 | * @example
2259 | *
2260 | * var ele;
2261 | *
2262 | * function setup() {
2263 | * background(250);
2264 | *
2265 | * //p5.MediaElement objects are usually created
2266 | * //by calling the createAudio(), createVideo(),
2267 | * //and createCapture() functions.
2268 | *
2269 | * //In this example we create
2270 | * //a new p5.MediaElement via createAudio().
2271 | * ele = createAudio('assets/beat.mp3');
2272 | *
2273 | * //We'll set up our example so that
2274 | * //when you click on the text,
2275 | * //an alert box displays the MediaElement's
2276 | * //src field.
2277 | * textAlign(CENTER);
2278 | * text('Click Me!', width / 2, height / 2);
2279 | * }
2280 | *
2281 | * function mouseClicked() {
2282 | * //here we test if the mouse is over the
2283 | * //canvas element when it's clicked
2284 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
2285 | * //Show our p5.MediaElement's src field
2286 | * alert(ele.src);
2287 | * }
2288 | * }
2289 | *
2290 | */
2291 | Object.defineProperty(self, 'src', {
2292 | get: function() {
2293 | var firstChildSrc = self.elt.children[0].src;
2294 | var srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
2295 | var ret =
2296 | firstChildSrc === window.location.href ? srcVal : firstChildSrc;
2297 | return ret;
2298 | },
2299 | set: function(newValue) {
2300 | for (var i = 0; i < self.elt.children.length; i++) {
2301 | self.elt.removeChild(self.elt.children[i]);
2302 | }
2303 | var source = document.createElement('source');
2304 | source.src = newValue;
2305 | elt.appendChild(source);
2306 | self.elt.src = newValue;
2307 | self.modified = true;
2308 | }
2309 | });
2310 |
2311 | // private _onended callback, set by the method: onended(callback)
2312 | self._onended = function() {};
2313 | self.elt.onended = function() {
2314 | self._onended(self);
2315 | };
2316 | };
2317 | p5.MediaElement.prototype = Object.create(p5.Element.prototype);
2318 |
2319 | /**
2320 | * Play an HTML5 media element.
2321 | *
2322 | * @method play
2323 | * @chainable
2324 | * @example
2325 | *
2326 | * var ele;
2327 | *
2328 | * function setup() {
2329 | * //p5.MediaElement objects are usually created
2330 | * //by calling the createAudio(), createVideo(),
2331 | * //and createCapture() functions.
2332 | *
2333 | * //In this example we create
2334 | * //a new p5.MediaElement via createAudio().
2335 | * ele = createAudio('assets/beat.mp3');
2336 | *
2337 | * background(250);
2338 | * textAlign(CENTER);
2339 | * text('Click to Play!', width / 2, height / 2);
2340 | * }
2341 | *
2342 | * function mouseClicked() {
2343 | * //here we test if the mouse is over the
2344 | * //canvas element when it's clicked
2345 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
2346 | * //Here we call the play() function on
2347 | * //the p5.MediaElement we created above.
2348 | * //This will start the audio sample.
2349 | * ele.play();
2350 | *
2351 | * background(200);
2352 | * text('You clicked Play!', width / 2, height / 2);
2353 | * }
2354 | * }
2355 | *
2356 | */
2357 | p5.MediaElement.prototype.play = function() {
2358 | if (this.elt.currentTime === this.elt.duration) {
2359 | this.elt.currentTime = 0;
2360 | }
2361 | var promise;
2362 | if (this.elt.readyState > 1) {
2363 | promise = this.elt.play();
2364 | } else {
2365 | // in Chrome, playback cannot resume after being stopped and must reload
2366 | this.elt.load();
2367 | promise = this.elt.play();
2368 | }
2369 | if (promise && promise.catch) {
2370 | promise.catch(function(e) {
2371 | console.log(
2372 | 'WARN: Element play method raised an error asynchronously',
2373 | e
2374 | );
2375 | });
2376 | }
2377 | return this;
2378 | };
2379 |
2380 | /**
2381 | * Stops an HTML5 media element (sets current time to zero).
2382 | *
2383 | * @method stop
2384 | * @chainable
2385 | * @example
2386 | *
2387 | * //This example both starts
2388 | * //and stops a sound sample
2389 | * //when the user clicks the canvas
2390 | *
2391 | * //We will store the p5.MediaElement
2392 | * //object in here
2393 | * var ele;
2394 | *
2395 | * //while our audio is playing,
2396 | * //this will be set to true
2397 | * var sampleIsPlaying = false;
2398 | *
2399 | * function setup() {
2400 | * //Here we create a p5.MediaElement object
2401 | * //using the createAudio() function.
2402 | * ele = createAudio('assets/beat.mp3');
2403 | * background(200);
2404 | * textAlign(CENTER);
2405 | * text('Click to play!', width / 2, height / 2);
2406 | * }
2407 | *
2408 | * function mouseClicked() {
2409 | * //here we test if the mouse is over the
2410 | * //canvas element when it's clicked
2411 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
2412 | * background(200);
2413 | *
2414 | * if (sampleIsPlaying) {
2415 | * //if the sample is currently playing
2416 | * //calling the stop() function on
2417 | * //our p5.MediaElement will stop
2418 | * //it and reset its current
2419 | * //time to 0 (i.e. it will start
2420 | * //at the beginning the next time
2421 | * //you play it)
2422 | * ele.stop();
2423 | *
2424 | * sampleIsPlaying = false;
2425 | * text('Click to play!', width / 2, height / 2);
2426 | * } else {
2427 | * //loop our sound element until we
2428 | * //call ele.stop() on it.
2429 | * ele.loop();
2430 | *
2431 | * sampleIsPlaying = true;
2432 | * text('Click to stop!', width / 2, height / 2);
2433 | * }
2434 | * }
2435 | * }
2436 | *
2437 | */
2438 | p5.MediaElement.prototype.stop = function() {
2439 | this.elt.pause();
2440 | this.elt.currentTime = 0;
2441 | return this;
2442 | };
2443 |
2444 | /**
2445 | * Pauses an HTML5 media element.
2446 | *
2447 | * @method pause
2448 | * @chainable
2449 | * @example
2450 | *
2451 | * //This example both starts
2452 | * //and pauses a sound sample
2453 | * //when the user clicks the canvas
2454 | *
2455 | * //We will store the p5.MediaElement
2456 | * //object in here
2457 | * var ele;
2458 | *
2459 | * //while our audio is playing,
2460 | * //this will be set to true
2461 | * var sampleIsPlaying = false;
2462 | *
2463 | * function setup() {
2464 | * //Here we create a p5.MediaElement object
2465 | * //using the createAudio() function.
2466 | * ele = createAudio('assets/lucky_dragons.mp3');
2467 | * background(200);
2468 | * textAlign(CENTER);
2469 | * text('Click to play!', width / 2, height / 2);
2470 | * }
2471 | *
2472 | * function mouseClicked() {
2473 | * //here we test if the mouse is over the
2474 | * //canvas element when it's clicked
2475 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
2476 | * background(200);
2477 | *
2478 | * if (sampleIsPlaying) {
2479 | * //Calling pause() on our
2480 | * //p5.MediaElement will stop it
2481 | * //playing, but when we call the
2482 | * //loop() or play() functions
2483 | * //the sample will start from
2484 | * //where we paused it.
2485 | * ele.pause();
2486 | *
2487 | * sampleIsPlaying = false;
2488 | * text('Click to resume!', width / 2, height / 2);
2489 | * } else {
2490 | * //loop our sound element until we
2491 | * //call ele.pause() on it.
2492 | * ele.loop();
2493 | *
2494 | * sampleIsPlaying = true;
2495 | * text('Click to pause!', width / 2, height / 2);
2496 | * }
2497 | * }
2498 | * }
2499 | *
2500 | */
2501 | p5.MediaElement.prototype.pause = function() {
2502 | this.elt.pause();
2503 | return this;
2504 | };
2505 |
2506 | /**
2507 | * Set 'loop' to true for an HTML5 media element, and starts playing.
2508 | *
2509 | * @method loop
2510 | * @chainable
2511 | * @example
2512 | *
2513 | * //Clicking the canvas will loop
2514 | * //the audio sample until the user
2515 | * //clicks again to stop it
2516 | *
2517 | * //We will store the p5.MediaElement
2518 | * //object in here
2519 | * var ele;
2520 | *
2521 | * //while our audio is playing,
2522 | * //this will be set to true
2523 | * var sampleIsLooping = false;
2524 | *
2525 | * function setup() {
2526 | * //Here we create a p5.MediaElement object
2527 | * //using the createAudio() function.
2528 | * ele = createAudio('assets/lucky_dragons.mp3');
2529 | * background(200);
2530 | * textAlign(CENTER);
2531 | * text('Click to loop!', width / 2, height / 2);
2532 | * }
2533 | *
2534 | * function mouseClicked() {
2535 | * //here we test if the mouse is over the
2536 | * //canvas element when it's clicked
2537 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
2538 | * background(200);
2539 | *
2540 | * if (!sampleIsLooping) {
2541 | * //loop our sound element until we
2542 | * //call ele.stop() on it.
2543 | * ele.loop();
2544 | *
2545 | * sampleIsLooping = true;
2546 | * text('Click to stop!', width / 2, height / 2);
2547 | * } else {
2548 | * ele.stop();
2549 | *
2550 | * sampleIsLooping = false;
2551 | * text('Click to loop!', width / 2, height / 2);
2552 | * }
2553 | * }
2554 | * }
2555 | *
2556 | */
2557 | p5.MediaElement.prototype.loop = function() {
2558 | this.elt.setAttribute('loop', true);
2559 | this.play();
2560 | return this;
2561 | };
2562 | /**
2563 | * Set 'loop' to false for an HTML5 media element. Element will stop
2564 | * when it reaches the end.
2565 | *
2566 | * @method noLoop
2567 | * @chainable
2568 | * @example
2569 | *
2570 | * //This example both starts
2571 | * //and stops loop of sound sample
2572 | * //when the user clicks the canvas
2573 | *
2574 | * //We will store the p5.MediaElement
2575 | * //object in here
2576 | * var ele;
2577 | * //while our audio is playing,
2578 | * //this will be set to true
2579 | * var sampleIsPlaying = false;
2580 | *
2581 | * function setup() {
2582 | * //Here we create a p5.MediaElement object
2583 | * //using the createAudio() function.
2584 | * ele = createAudio('assets/beat.mp3');
2585 | * background(200);
2586 | * textAlign(CENTER);
2587 | * text('Click to play!', width / 2, height / 2);
2588 | * }
2589 | *
2590 | * function mouseClicked() {
2591 | * //here we test if the mouse is over the
2592 | * //canvas element when it's clicked
2593 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
2594 | * background(200);
2595 | *
2596 | * if (sampleIsPlaying) {
2597 | * ele.noLoop();
2598 | * text('No more Loops!', width / 2, height / 2);
2599 | * } else {
2600 | * ele.loop();
2601 | * sampleIsPlaying = true;
2602 | * text('Click to stop looping!', width / 2, height / 2);
2603 | * }
2604 | * }
2605 | * }
2606 | *
2607 | *
2608 | */
2609 | p5.MediaElement.prototype.noLoop = function() {
2610 | this.elt.setAttribute('loop', false);
2611 | return this;
2612 | };
2613 |
2614 | /**
2615 | * Set HTML5 media element to autoplay or not.
2616 | *
2617 | * @method autoplay
2618 | * @param {Boolean} autoplay whether the element should autoplay
2619 | * @chainable
2620 | */
2621 | p5.MediaElement.prototype.autoplay = function(val) {
2622 | this.elt.setAttribute('autoplay', val);
2623 | return this;
2624 | };
2625 |
2626 | /**
2627 | * Sets volume for this HTML5 media element. If no argument is given,
2628 | * returns the current volume.
2629 | *
2630 | * @method volume
2631 | * @return {Number} current volume
2632 | *
2633 | * @example
2634 | *
2635 | * var ele;
2636 | * function setup() {
2637 | * // p5.MediaElement objects are usually created
2638 | * // by calling the createAudio(), createVideo(),
2639 | * // and createCapture() functions.
2640 | * // In this example we create
2641 | * // a new p5.MediaElement via createAudio().
2642 | * ele = createAudio('assets/lucky_dragons.mp3');
2643 | * background(250);
2644 | * textAlign(CENTER);
2645 | * text('Click to Play!', width / 2, height / 2);
2646 | * }
2647 | * function mouseClicked() {
2648 | * // Here we call the volume() function
2649 | * // on the sound element to set its volume
2650 | * // Volume must be between 0.0 and 1.0
2651 | * ele.volume(0.2);
2652 | * ele.play();
2653 | * background(200);
2654 | * text('You clicked Play!', width / 2, height / 2);
2655 | * }
2656 | *
2657 | *
2658 | * var audio;
2659 | * var counter = 0;
2660 | *
2661 | * function loaded() {
2662 | * audio.play();
2663 | * }
2664 | *
2665 | * function setup() {
2666 | * audio = createAudio('assets/lucky_dragons.mp3', loaded);
2667 | * textAlign(CENTER);
2668 | * }
2669 | *
2670 | * function draw() {
2671 | * if (counter === 0) {
2672 | * background(0, 255, 0);
2673 | * text('volume(0.9)', width / 2, height / 2);
2674 | * } else if (counter === 1) {
2675 | * background(255, 255, 0);
2676 | * text('volume(0.5)', width / 2, height / 2);
2677 | * } else if (counter === 2) {
2678 | * background(255, 0, 0);
2679 | * text('volume(0.1)', width / 2, height / 2);
2680 | * }
2681 | * }
2682 | *
2683 | * function mousePressed() {
2684 | * counter++;
2685 | * if (counter === 0) {
2686 | * audio.volume(0.9);
2687 | * } else if (counter === 1) {
2688 | * audio.volume(0.5);
2689 | * } else if (counter === 2) {
2690 | * audio.volume(0.1);
2691 | * } else {
2692 | * counter = 0;
2693 | * audio.volume(0.9);
2694 | * }
2695 | * }
2696 | *
2697 | *
2698 | */
2699 | /**
2700 | * @method volume
2701 | * @param {Number} val volume between 0.0 and 1.0
2702 | * @chainable
2703 | */
2704 | p5.MediaElement.prototype.volume = function(val) {
2705 | if (typeof val === 'undefined') {
2706 | return this.elt.volume;
2707 | } else {
2708 | this.elt.volume = val;
2709 | }
2710 | };
2711 |
2712 | /**
2713 | * If no arguments are given, returns the current playback speed of the
2714 | * element. The speed parameter sets the speed where 2.0 will play the
2715 | * element twice as fast, 0.5 will play at half the speed, and -1 will play
2716 | * the element in normal speed in reverse.(Note that not all browsers support
2717 | * backward playback and even if they do, playback might not be smooth.)
2718 | *
2719 | * @method speed
2720 | * @return {Number} current playback speed of the element
2721 | *
2722 | * @example
2723 | *
2724 | * //Clicking the canvas will loop
2725 | * //the audio sample until the user
2726 | * //clicks again to stop it
2727 | *
2728 | * //We will store the p5.MediaElement
2729 | * //object in here
2730 | * var ele;
2731 | * var button;
2732 | *
2733 | * function setup() {
2734 | * createCanvas(710, 400);
2735 | * //Here we create a p5.MediaElement object
2736 | * //using the createAudio() function.
2737 | * ele = createAudio('assets/beat.mp3');
2738 | * ele.loop();
2739 | * background(200);
2740 | *
2741 | * button = createButton('2x speed');
2742 | * button.position(100, 68);
2743 | * button.mousePressed(twice_speed);
2744 | *
2745 | * button = createButton('half speed');
2746 | * button.position(200, 68);
2747 | * button.mousePressed(half_speed);
2748 | *
2749 | * button = createButton('reverse play');
2750 | * button.position(300, 68);
2751 | * button.mousePressed(reverse_speed);
2752 | *
2753 | * button = createButton('STOP');
2754 | * button.position(400, 68);
2755 | * button.mousePressed(stop_song);
2756 | *
2757 | * button = createButton('PLAY!');
2758 | * button.position(500, 68);
2759 | * button.mousePressed(play_speed);
2760 | * }
2761 | *
2762 | * function twice_speed() {
2763 | * ele.speed(2);
2764 | * }
2765 | *
2766 | * function half_speed() {
2767 | * ele.speed(0.5);
2768 | * }
2769 | *
2770 | * function reverse_speed() {
2771 | * ele.speed(-1);
2772 | * }
2773 | *
2774 | * function stop_song() {
2775 | * ele.stop();
2776 | * }
2777 | *
2778 | * function play_speed() {
2779 | * ele.play();
2780 | * }
2781 | *
2782 | */
2783 | /**
2784 | * @method speed
2785 | * @param {Number} speed speed multiplier for element playback
2786 | * @chainable
2787 | */
2788 | p5.MediaElement.prototype.speed = function(val) {
2789 | if (typeof val === 'undefined') {
2790 | return this.presetPlaybackRate || this.elt.playbackRate;
2791 | } else {
2792 | if (this.loadedmetadata) {
2793 | this.elt.playbackRate = val;
2794 | } else {
2795 | this.presetPlaybackRate = val;
2796 | }
2797 | }
2798 | };
2799 |
2800 | /**
2801 | * If no arguments are given, returns the current time of the element.
2802 | * If an argument is given the current time of the element is set to it.
2803 | *
2804 | * @method time
2805 | * @return {Number} current time (in seconds)
2806 | *
2807 | * @example
2808 | *
2809 | * var ele;
2810 | * var beginning = true;
2811 | * function setup() {
2812 | * //p5.MediaElement objects are usually created
2813 | * //by calling the createAudio(), createVideo(),
2814 | * //and createCapture() functions.
2815 | *
2816 | * //In this example we create
2817 | * //a new p5.MediaElement via createAudio().
2818 | * ele = createAudio('assets/lucky_dragons.mp3');
2819 | * background(250);
2820 | * textAlign(CENTER);
2821 | * text('start at beginning', width / 2, height / 2);
2822 | * }
2823 | *
2824 | * // this function fires with click anywhere
2825 | * function mousePressed() {
2826 | * if (beginning === true) {
2827 | * // here we start the sound at the beginning
2828 | * // time(0) is not necessary here
2829 | * // as this produces the same result as
2830 | * // play()
2831 | * ele.play().time(0);
2832 | * background(200);
2833 | * text('jump 2 sec in', width / 2, height / 2);
2834 | * beginning = false;
2835 | * } else {
2836 | * // here we jump 2 seconds into the sound
2837 | * ele.play().time(2);
2838 | * background(250);
2839 | * text('start at beginning', width / 2, height / 2);
2840 | * beginning = true;
2841 | * }
2842 | * }
2843 | *
2844 | */
2845 | /**
2846 | * @method time
2847 | * @param {Number} time time to jump to (in seconds)
2848 | * @chainable
2849 | */
2850 | p5.MediaElement.prototype.time = function(val) {
2851 | if (typeof val === 'undefined') {
2852 | return this.elt.currentTime;
2853 | } else {
2854 | this.elt.currentTime = val;
2855 | return this;
2856 | }
2857 | };
2858 |
2859 | /**
2860 | * Returns the duration of the HTML5 media element.
2861 | *
2862 | * @method duration
2863 | * @return {Number} duration
2864 | *
2865 | * @example
2866 | *
2867 | * var ele;
2868 | * function setup() {
2869 | * //p5.MediaElement objects are usually created
2870 | * //by calling the createAudio(), createVideo(),
2871 | * //and createCapture() functions.
2872 | * //In this example we create
2873 | * //a new p5.MediaElement via createAudio().
2874 | * ele = createAudio('assets/doorbell.mp3');
2875 | * background(250);
2876 | * textAlign(CENTER);
2877 | * text('Click to know the duration!', 10, 25, 70, 80);
2878 | * }
2879 | * function mouseClicked() {
2880 | * ele.play();
2881 | * background(200);
2882 | * //ele.duration dislpays the duration
2883 | * text(ele.duration() + ' seconds', width / 2, height / 2);
2884 | * }
2885 | *
2886 | */
2887 | p5.MediaElement.prototype.duration = function() {
2888 | return this.elt.duration;
2889 | };
2890 | p5.MediaElement.prototype.pixels = [];
2891 | p5.MediaElement.prototype._ensureCanvas = function() {
2892 | if (!this.canvas) {
2893 | this.canvas = document.createElement('canvas');
2894 | this.drawingContext = this.canvas.getContext('2d');
2895 | this.setModified(true);
2896 | }
2897 | if (this.loadedmetadata) {
2898 | // wait for metadata for w/h
2899 | if (this.canvas.width !== this.elt.width) {
2900 | this.canvas.width = this.elt.width;
2901 | this.canvas.height = this.elt.height;
2902 | this.width = this.canvas.width;
2903 | this.height = this.canvas.height;
2904 | this._pixelsDirty = true;
2905 | }
2906 |
2907 | var currentTime = this.elt.currentTime;
2908 | if (this._pixelsDirty || this._pixelsTime !== currentTime) {
2909 | // only update the pixels array if it's dirty, or
2910 | // if the video time has changed.
2911 | this._pixelsTime = currentTime;
2912 | this._pixelsDirty = true;
2913 |
2914 | this.drawingContext.drawImage(
2915 | this.elt,
2916 | 0,
2917 | 0,
2918 | this.canvas.width,
2919 | this.canvas.height
2920 | );
2921 | this.setModified(true);
2922 | }
2923 | }
2924 | };
2925 | p5.MediaElement.prototype.loadPixels = function() {
2926 | this._ensureCanvas();
2927 | return p5.Renderer2D.prototype.loadPixels.apply(this, arguments);
2928 | };
2929 | p5.MediaElement.prototype.updatePixels = function(x, y, w, h) {
2930 | if (this.loadedmetadata) {
2931 | // wait for metadata
2932 | this._ensureCanvas();
2933 | p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
2934 | }
2935 | this.setModified(true);
2936 | return this;
2937 | };
2938 | p5.MediaElement.prototype.get = function() {
2939 | this._ensureCanvas();
2940 | return p5.Renderer2D.prototype.get.apply(this, arguments);
2941 | };
2942 | p5.MediaElement.prototype._getPixel = function() {
2943 | this.loadPixels();
2944 | return p5.Renderer2D.prototype._getPixel.apply(this, arguments);
2945 | };
2946 |
2947 | p5.MediaElement.prototype.set = function(x, y, imgOrCol) {
2948 | if (this.loadedmetadata) {
2949 | // wait for metadata
2950 | this._ensureCanvas();
2951 | p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
2952 | this.setModified(true);
2953 | }
2954 | };
2955 | p5.MediaElement.prototype.copy = function() {
2956 | this._ensureCanvas();
2957 | p5.Renderer2D.prototype.copy.apply(this, arguments);
2958 | };
2959 | p5.MediaElement.prototype.mask = function() {
2960 | this.loadPixels();
2961 | this.setModified(true);
2962 | p5.Image.prototype.mask.apply(this, arguments);
2963 | };
2964 | /**
2965 | * helper method for web GL mode to figure out if the element
2966 | * has been modified and might need to be re-uploaded to texture
2967 | * memory between frames.
2968 | * @method isModified
2969 | * @private
2970 | * @return {boolean} a boolean indicating whether or not the
2971 | * image has been updated or modified since last texture upload.
2972 | */
2973 | p5.MediaElement.prototype.isModified = function() {
2974 | return this._modified;
2975 | };
2976 | /**
2977 | * helper method for web GL mode to indicate that an element has been
2978 | * changed or unchanged since last upload. gl texture upload will
2979 | * set this value to false after uploading the texture; or might set
2980 | * it to true if metadata has become available but there is no actual
2981 | * texture data available yet..
2982 | * @method setModified
2983 | * @param {boolean} val sets whether or not the element has been
2984 | * modified.
2985 | * @private
2986 | */
2987 | p5.MediaElement.prototype.setModified = function(value) {
2988 | this._modified = value;
2989 | };
2990 | /**
2991 | * Schedule an event to be called when the audio or video
2992 | * element reaches the end. If the element is looping,
2993 | * this will not be called. The element is passed in
2994 | * as the argument to the onended callback.
2995 | *
2996 | * @method onended
2997 | * @param {Function} callback function to call when the
2998 | * soundfile has ended. The
2999 | * media element will be passed
3000 | * in as the argument to the
3001 | * callback.
3002 | * @chainable
3003 | * @example
3004 | *
3005 | * function setup() {
3006 | * var audioEl = createAudio('assets/beat.mp3');
3007 | * audioEl.showControls();
3008 | * audioEl.onended(sayDone);
3009 | * }
3010 | *
3011 | * function sayDone(elt) {
3012 | * alert('done playing ' + elt.src);
3013 | * }
3014 | *
3015 | */
3016 | p5.MediaElement.prototype.onended = function(callback) {
3017 | this._onended = callback;
3018 | return this;
3019 | };
3020 |
3021 | /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
3022 |
3023 | /**
3024 | * Send the audio output of this element to a specified audioNode or
3025 | * p5.sound object. If no element is provided, connects to p5's master
3026 | * output. That connection is established when this method is first called.
3027 | * All connections are removed by the .disconnect() method.
3028 | *
3029 | * This method is meant to be used with the p5.sound.js addon library.
3030 | *
3031 | * @method connect
3032 | * @param {AudioNode|Object} audioNode AudioNode from the Web Audio API,
3033 | * or an object from the p5.sound library
3034 | */
3035 | p5.MediaElement.prototype.connect = function(obj) {
3036 | var audioContext, masterOutput;
3037 |
3038 | // if p5.sound exists, same audio context
3039 | if (typeof p5.prototype.getAudioContext === 'function') {
3040 | audioContext = p5.prototype.getAudioContext();
3041 | masterOutput = p5.soundOut.input;
3042 | } else {
3043 | try {
3044 | audioContext = obj.context;
3045 | masterOutput = audioContext.destination;
3046 | } catch (e) {
3047 | throw 'connect() is meant to be used with Web Audio API or p5.sound.js';
3048 | }
3049 | }
3050 |
3051 | // create a Web Audio MediaElementAudioSourceNode if none already exists
3052 | if (!this.audioSourceNode) {
3053 | this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
3054 |
3055 | // connect to master output when this method is first called
3056 | this.audioSourceNode.connect(masterOutput);
3057 | }
3058 |
3059 | // connect to object if provided
3060 | if (obj) {
3061 | if (obj.input) {
3062 | this.audioSourceNode.connect(obj.input);
3063 | } else {
3064 | this.audioSourceNode.connect(obj);
3065 | }
3066 | } else {
3067 | // otherwise connect to master output of p5.sound / AudioContext
3068 | this.audioSourceNode.connect(masterOutput);
3069 | }
3070 | };
3071 |
3072 | /**
3073 | * Disconnect all Web Audio routing, including to master output.
3074 | * This is useful if you want to re-route the output through
3075 | * audio effects, for example.
3076 | *
3077 | * @method disconnect
3078 | */
3079 | p5.MediaElement.prototype.disconnect = function() {
3080 | if (this.audioSourceNode) {
3081 | this.audioSourceNode.disconnect();
3082 | } else {
3083 | throw 'nothing to disconnect';
3084 | }
3085 | };
3086 |
3087 | /*** SHOW / HIDE CONTROLS ***/
3088 |
3089 | /**
3090 | * Show the default MediaElement controls, as determined by the web browser.
3091 | *
3092 | * @method showControls
3093 | * @example
3094 | *
3095 | * var ele;
3096 | * function setup() {
3097 | * //p5.MediaElement objects are usually created
3098 | * //by calling the createAudio(), createVideo(),
3099 | * //and createCapture() functions.
3100 | * //In this example we create
3101 | * //a new p5.MediaElement via createAudio()
3102 | * ele = createAudio('assets/lucky_dragons.mp3');
3103 | * background(200);
3104 | * textAlign(CENTER);
3105 | * text('Click to Show Controls!', 10, 25, 70, 80);
3106 | * }
3107 | * function mousePressed() {
3108 | * ele.showControls();
3109 | * background(200);
3110 | * text('Controls Shown', width / 2, height / 2);
3111 | * }
3112 | *
3113 | */
3114 | p5.MediaElement.prototype.showControls = function() {
3115 | // must set style for the element to show on the page
3116 | this.elt.style['text-align'] = 'inherit';
3117 | this.elt.controls = true;
3118 | };
3119 |
3120 | /**
3121 | * Hide the default mediaElement controls.
3122 | * @method hideControls
3123 | * @example
3124 | *
3125 | * var ele;
3126 | * function setup() {
3127 | * //p5.MediaElement objects are usually created
3128 | * //by calling the createAudio(), createVideo(),
3129 | * //and createCapture() functions.
3130 | * //In this example we create
3131 | * //a new p5.MediaElement via createAudio()
3132 | * ele = createAudio('assets/lucky_dragons.mp3');
3133 | * ele.showControls();
3134 | * background(200);
3135 | * textAlign(CENTER);
3136 | * text('Click to hide Controls!', 10, 25, 70, 80);
3137 | * }
3138 | * function mousePressed() {
3139 | * ele.hideControls();
3140 | * background(200);
3141 | * text('Controls hidden', width / 2, height / 2);
3142 | * }
3143 | *
3144 | */
3145 | p5.MediaElement.prototype.hideControls = function() {
3146 | this.elt.controls = false;
3147 | };
3148 |
3149 | /*** SCHEDULE EVENTS ***/
3150 |
3151 | // Cue inspired by JavaScript setTimeout, and the
3152 | // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
3153 | var Cue = function(callback, time, id, val) {
3154 | this.callback = callback;
3155 | this.time = time;
3156 | this.id = id;
3157 | this.val = val;
3158 | };
3159 |
3160 | /**
3161 | * Schedule events to trigger every time a MediaElement
3162 | * (audio/video) reaches a playback cue point.
3163 | *
3164 | * Accepts a callback function, a time (in seconds) at which to trigger
3165 | * the callback, and an optional parameter for the callback.
3166 | *
3167 | * Time will be passed as the first parameter to the callback function,
3168 | * and param will be the second parameter.
3169 | *
3170 | *
3171 | * @method addCue
3172 | * @param {Number} time Time in seconds, relative to this media
3173 | * element's playback. For example, to trigger
3174 | * an event every time playback reaches two
3175 | * seconds, pass in the number 2. This will be
3176 | * passed as the first parameter to
3177 | * the callback function.
3178 | * @param {Function} callback Name of a function that will be
3179 | * called at the given time. The callback will
3180 | * receive time and (optionally) param as its
3181 | * two parameters.
3182 | * @param {Object} [value] An object to be passed as the
3183 | * second parameter to the
3184 | * callback function.
3185 | * @return {Number} id ID of this cue,
3186 | * useful for removeCue(id)
3187 | * @example
3188 | *
3189 | * //
3190 | * //
3191 | * function setup() {
3192 | * noCanvas();
3193 | *
3194 | * var audioEl = createAudio('assets/beat.mp3');
3195 | * audioEl.showControls();
3196 | *
3197 | * // schedule three calls to changeBackground
3198 | * audioEl.addCue(0.5, changeBackground, color(255, 0, 0));
3199 | * audioEl.addCue(1.0, changeBackground, color(0, 255, 0));
3200 | * audioEl.addCue(2.5, changeBackground, color(0, 0, 255));
3201 | * audioEl.addCue(3.0, changeBackground, color(0, 255, 255));
3202 | * audioEl.addCue(4.2, changeBackground, color(255, 255, 0));
3203 | * audioEl.addCue(5.0, changeBackground, color(255, 255, 0));
3204 | * }
3205 | *
3206 | * function changeBackground(val) {
3207 | * background(val);
3208 | * }
3209 | *
3210 | */
3211 | p5.MediaElement.prototype.addCue = function(time, callback, val) {
3212 | var id = this._cueIDCounter++;
3213 |
3214 | var cue = new Cue(callback, time, id, val);
3215 | this._cues.push(cue);
3216 |
3217 | if (!this.elt.ontimeupdate) {
3218 | this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
3219 | }
3220 |
3221 | return id;
3222 | };
3223 |
3224 | /**
3225 | * Remove a callback based on its ID. The ID is returned by the
3226 | * addCue method.
3227 | * @method removeCue
3228 | * @param {Number} id ID of the cue, as returned by addCue
3229 | * @example
3230 | *
3231 | * var audioEl, id1, id2;
3232 | * function setup() {
3233 | * background(255, 255, 255);
3234 | * audioEl = createAudio('assets/beat.mp3');
3235 | * audioEl.showControls();
3236 | * // schedule five calls to changeBackground
3237 | * id1 = audioEl.addCue(0.5, changeBackground, color(255, 0, 0));
3238 | * audioEl.addCue(1.0, changeBackground, color(0, 255, 0));
3239 | * audioEl.addCue(2.5, changeBackground, color(0, 0, 255));
3240 | * audioEl.addCue(3.0, changeBackground, color(0, 255, 255));
3241 | * id2 = audioEl.addCue(4.2, changeBackground, color(255, 255, 0));
3242 | * text('Click to remove first and last Cue!', 10, 25, 70, 80);
3243 | * }
3244 | * function mousePressed() {
3245 | * audioEl.removeCue(id1);
3246 | * audioEl.removeCue(id2);
3247 | * }
3248 | * function changeBackground(val) {
3249 | * background(val);
3250 | * }
3251 | *
3252 | */
3253 | p5.MediaElement.prototype.removeCue = function(id) {
3254 | for (var i = 0; i < this._cues.length; i++) {
3255 | if (this._cues[i].id === id) {
3256 | console.log(id);
3257 | this._cues.splice(i, 1);
3258 | }
3259 | }
3260 |
3261 | if (this._cues.length === 0) {
3262 | this.elt.ontimeupdate = null;
3263 | }
3264 | };
3265 |
3266 | /**
3267 | * Remove all of the callbacks that had originally been scheduled
3268 | * via the addCue method.
3269 | * @method clearCues
3270 | * @param {Number} id ID of the cue, as returned by addCue
3271 | * @example
3272 | *
3273 | * var audioEl;
3274 | * function setup() {
3275 | * background(255, 255, 255);
3276 | * audioEl = createAudio('assets/beat.mp3');
3277 | * //Show the default MediaElement controls, as determined by the web browser
3278 | * audioEl.showControls();
3279 | * // schedule calls to changeBackground
3280 | * background(200);
3281 | * text('Click to change Cue!', 10, 25, 70, 80);
3282 | * audioEl.addCue(0.5, changeBackground, color(255, 0, 0));
3283 | * audioEl.addCue(1.0, changeBackground, color(0, 255, 0));
3284 | * audioEl.addCue(2.5, changeBackground, color(0, 0, 255));
3285 | * audioEl.addCue(3.0, changeBackground, color(0, 255, 255));
3286 | * audioEl.addCue(4.2, changeBackground, color(255, 255, 0));
3287 | * }
3288 | * function mousePressed() {
3289 | * // here we clear the scheduled callbacks
3290 | * audioEl.clearCues();
3291 | * // then we add some more callbacks
3292 | * audioEl.addCue(1, changeBackground, color(2, 2, 2));
3293 | * audioEl.addCue(3, changeBackground, color(255, 255, 0));
3294 | * }
3295 | * function changeBackground(val) {
3296 | * background(val);
3297 | * }
3298 | *
3299 | */
3300 | p5.MediaElement.prototype.clearCues = function() {
3301 | this._cues = [];
3302 | this.elt.ontimeupdate = null;
3303 | };
3304 |
3305 | // private method that checks for cues to be fired if events
3306 | // have been scheduled using addCue(callback, time).
3307 | p5.MediaElement.prototype._onTimeUpdate = function() {
3308 | var playbackTime = this.time();
3309 |
3310 | for (var i = 0; i < this._cues.length; i++) {
3311 | var callbackTime = this._cues[i].time;
3312 | var val = this._cues[i].val;
3313 |
3314 | if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
3315 | // pass the scheduled callbackTime as parameter to the callback
3316 | this._cues[i].callback(val);
3317 | }
3318 | }
3319 |
3320 | this._prevTime = playbackTime;
3321 | };
3322 |
3323 | /**
3324 | * Base class for a file.
3325 | * Used for Element.drop and createFileInput.
3326 | *
3327 | * @class p5.File
3328 | * @constructor
3329 | * @param {File} file File that is wrapped
3330 | */
3331 | p5.File = function(file, pInst) {
3332 | /**
3333 | * Underlying File object. All normal File methods can be called on this.
3334 | *
3335 | * @property file
3336 | */
3337 | this.file = file;
3338 |
3339 | this._pInst = pInst;
3340 |
3341 | // Splitting out the file type into two components
3342 | // This makes determining if image or text etc simpler
3343 | var typeList = file.type.split('/');
3344 | /**
3345 | * File type (image, text, etc.)
3346 | *
3347 | * @property type
3348 | */
3349 | this.type = typeList[0];
3350 | /**
3351 | * File subtype (usually the file extension jpg, png, xml, etc.)
3352 | *
3353 | * @property subtype
3354 | */
3355 | this.subtype = typeList[1];
3356 | /**
3357 | * File name
3358 | *
3359 | * @property name
3360 | */
3361 | this.name = file.name;
3362 | /**
3363 | * File size
3364 | *
3365 | * @property size
3366 | */
3367 | this.size = file.size;
3368 |
3369 | /**
3370 | * URL string containing image data.
3371 | *
3372 | * @property data
3373 | */
3374 | this.data = undefined;
3375 | };
3376 |
3377 | p5.File._createLoader = function(theFile, callback) {
3378 | var reader = new FileReader();
3379 | reader.onload = function(e) {
3380 | var p5file = new p5.File(theFile);
3381 | p5file.data = e.target.result;
3382 | callback(p5file);
3383 | };
3384 | return reader;
3385 | };
3386 |
3387 | p5.File._load = function(f, callback) {
3388 | // Text or data?
3389 | // This should likely be improved
3390 | if (/^text\//.test(f.type)) {
3391 | p5.File._createLoader(f, callback).readAsText(f);
3392 | } else if (!/^(video|audio)\//.test(f.type)) {
3393 | p5.File._createLoader(f, callback).readAsDataURL(f);
3394 | } else {
3395 | var file = new p5.File(f);
3396 | file.data = URL.createObjectURL(f);
3397 | callback(file);
3398 | }
3399 | };
3400 | });
3401 |
--------------------------------------------------------------------------------