简体   繁体   English

HTML Canvas和Javascript-通过选择触发音频(从多个位置)

[英]HTML Canvas & Javascript - Triggering Audio by Selection (From Multiple Places)

I have a selection menu in my HTML canvas that I would like to trigger corresponding audio files. 我的HTML画布中有一个选择菜单,我想触发相应的音频文件。 I have tried implementing this by declaring the images inside the if (this.hovered) & (this.clicked) part of the makeSelection function within the selectionForMenu prototype, such that on each new selection the selected audio file is redefined, but this causes problems like slow loading and overlapping audio. 我尝试通过在selectionForMenu原型内的makeSelection函数的if (this.hovered)(this.clicked)部分中声明图像来实现此目的,以便在每个新选择上重新定义所选音频文件,但这会导致问题例如缓慢加载和重叠音频。 It is also problematic as I am trying to get the speaker button at the bottom of the screen to play the audio corresponding to the current selection too, so if it is only defined within that function it is not accessible to the makeButton function. 这也是有问题的,因为我试图使屏幕底部的扬声器按钮也播放与当前选择相对应的音频,因此,如果仅在该函数中定义它,则makeButton函数将无法访问它。

You can see the selection menu and speaker button in the snippet below. 您可以在下面的代码段中看到选择菜单和扬声器按钮。 Each new selection in the menu should play once an audio file that corresponds to it (which I have not been able to add to this demonstration). 菜单中的每个新选择都应播放一次与其对应的音频文件(我无法将其添加到此演示中)。 It can be replayed by re-clicking the selection or clicking the speaker button, but each click should only provoke one play of the audio and of course overlapping is undesired. 可以通过重新单击所选内容或单击扬声器按钮来重播它,但是每次单击只能激发音频的播放,当然不希望出现重叠。 Any help will be appreciated. 任何帮助将不胜感激。

 var c=document.getElementById('game'), canvasX=c.offsetLeft, canvasY=c.offsetTop, ctx=c.getContext('2d'); var button = function(id, x, strokeColor) { this.id = id; this.x = x; this.strokeColor = strokeColor; this.hovered = false; this.clicked = false; } button.prototype.makeInteractiveButton = function() { if (this.hovered) { if (this.clicked) { this.fillColor = '#DFBCDE'; } else { this.fillColor = '#CA92C8' } } else { this.fillColor = '#BC77BA' } ctx.strokeStyle=this.strokeColor; ctx.fillStyle=this.fillColor; ctx.beginPath(); ctx.lineWidth='5'; ctx.arc(this.x, 475, 20, 0, 2*Math.PI); ctx.closePath(); ctx.stroke(); ctx.fill(); } button.prototype.hitTest = function(x, y) { return (Math.pow(x-this.x, 2) + Math.pow(y-475, 2) < Math.pow(20, 2)); } var selectionForMenu = function(id, text, y) { this.id = id; this.text = text; this.y = y; this.hovered = false; this.clicked = false; this.lastClicked = false; } selectionForMenu.prototype.makeSelection = function() { var fillColor='#A84FA5'; if (this.hovered) { if (this.clicked) { if (this.lastClicked) { fillColor='#E4C7E2'; } else { fillColor='#D5A9D3'; } } else if (this.lastClicked) { fillColor='#D3A4D0'; } else { fillColor='#BA74B7'; } } else if (this.lastClicked) { fillColor='#C78DC5'; } else { fillColor='#A84FA5'; } ctx.beginPath(); ctx.fillStyle=fillColor; ctx.fillRect(0, this.y, 350, 30) ctx.stroke(); ctx.font='10px Noto Sans'; ctx.fillStyle='white'; ctx.textAlign='left'; ctx.fillText(this.text, 10, this.y+19); } selectionForMenu.prototype.hitTest = function(x, y) { return (x >= 0) && (x <= (350)) && (y >= this.y) && (y <= (this.y+30)) && !((x >= 0) && (y > 450)); } var Paint = function(element) { this.element = element; this.shapes = []; } Paint.prototype.addShape = function(shape) { this.shapes.push(shape); } Paint.prototype.render = function() { ctx.clearRect(0, 0, this.element.width, this.element.height); for (var i=0; i<this.shapes.length; i++) { try { this.shapes[i].makeSelection(); } catch(err) {} } ctx.beginPath(); ctx.fillStyle='#BC77BA'; ctx.fillRect(0, 450, 750, 50); ctx.stroke(); for (var i=0; i<this.shapes.length; i++) { try { this.shapes[i].makeInteractiveButton(); } catch(err) {} } var speaker = new Image(25, 25); speaker.src='https://i.stack.imgur.com/lXg2I.png'; ctx.drawImage(speaker, 162.5, 462.5); } Paint.prototype.setHovered = function(shape) { for (var i=0; i<this.shapes.length; i++) { this.shapes[i].hovered = this.shapes[i] == shape; } this.render(); } Paint.prototype.setClicked = function(shape) { for (var i=0; i<this.shapes.length; i++) { this.shapes[i].clicked = this.shapes[i] == shape; } this.render(); } Paint.prototype.setUnclicked = function(shape) { for (var i=0; i<this.shapes.length; i++) { this.shapes[i].clicked = false; if (Number.isInteger(this.shapes[i].id)) { this.shapes[i].lastClicked = this.shapes[i] == shape; } } this.render(); } Paint.prototype.select = function(x, y) { for (var i=this.shapes.length-1; i >= 0; i--) { if (this.shapes[i].hitTest(x, y)) { return this.shapes[i]; } } return null } var paint = new Paint(c); var btn = new button('speaker', 175, '#FFFCF8'); var selection = []; for (i=0; i<15; i++) { selection.push(new selectionForMenu(i+1, i, i*30)); } paint.addShape(btn); for (i=0; i<15; i++) { paint.addShape(selection[i]) } paint.render(); function mouseDown(event) { var x = event.x - canvasX; var y = event.y - canvasY; var shape = paint.select(x, y); paint.setClicked(shape); } function mouseUp(event) { var x = event.x - canvasX; var y = event.y - canvasY; var shape = paint.select(x, y); paint.setUnclicked(shape); } function mouseMove(event) { var x = event.x - canvasX; var y = event.y - canvasY; var shape = paint.select(x, y); paint.setHovered(shape); } c.addEventListener('mousedown', mouseDown); c.addEventListener('mouseup', mouseUp); c.addEventListener('mousemove', mouseMove); 
 canvas { z-index: -1; margin: 1em auto; border: 1px solid black; display: block; background: #9F3A9B; } img { z-index: 0; position: absolute; pointer-events: none; } #speaker { top: 480px; left: 592px; } #snail { top: 475px; left: 637.5px; } 
 <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>uTalk Demo</title> <link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'></style> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> </head> <body> <canvas id="game" width = "350" height = "500"></canvas> <script type='text/javascript' src='wordpractice copy.js'></script> </body> </html> 

You could create an Audio Loader, that loads all the audios and keeps track of them: 您可以创建一个音频加载器,以加载所有音频并跟踪它们:

function load(srcs){
  var obj={};
  srcs.forEach(src=>obj[src]=new Audio(src));
  return obj;
}

Then you could do sth like this onload: 然后,您可以执行以下onload操作:

var audios=load(["audio1.mp3", "audio2.mp3"]);

And later: 然后:

(audios[src] || new Audio(src)).play();

This will just load the audio if it isnt already in the audios object. 如果音频对象中尚未存在音频,则只会加载音频。

When you want responsiveness with audio, forget about MediaElements, and go with the Web Audio API. 当您想要对音频进行响应时,就不用考虑MediaElements了,而是使用Web Audio API。 MediaElements ( <audio> and <video> ) are slow, and http caching is an nightmare. MediaElements( <audio><video> )很慢,并且HTTP缓存是一场噩梦。

With the Web Audio API, you can first download all you media as arrayBuffers, decode their audio data to AudioBuffers, that you'll attach to your js objects. 使用Web Audio API,您可以首先将所有媒体下载为arrayBuffer,将其音频数据解码为AudioBuffer,然后将其附加到js对象。 From there, you'll be able to play new instances of these media in µs. 从那里,您将能够在µs内播放这些媒体的新实例。

Beware, ES6 syntax below, for older browsers, here is an ES5 rewrite , also note that Internet Explorer < Edge does not support the Web Audio API, if you need to support these browsers, you'll have to make an fallback with audio elements. 请注意,下面的ES6语法,对于较旧的浏览器, 这是ES5重写 ,还请注意Internet Explorer <Edge 支持Web Audio API,如果需要支持这些浏览器,则必须使用音频元素进行后备。

 (function myFirstDrumKit() { const db_url = 'https://dl.dropboxusercontent.com/s/'; // all our medias are stored on dropbox // we'll need to first load all the audios function initAudios() { const promises = drum.parts.map(part => { return fetch(db_url + part.audio_src) // fetch the file .then(resp => resp.arrayBuffer()) // as an arrayBuffer .then(buf => drum.a_ctx.decodeAudioData(buf)) // then decode its audio data .then(AudioBuf => { part.buf = AudioBuf; // store the audioBuffer (won't change) return Promise.resolve(part); // done }); }); return Promise.all(promises); // when all are loaded } function initImages() { // in this version we have only an static image, // but we could have multiple per parts, with the same logic as for audios var img = new Image(); img.src = db_url + drum.bg_src; drum.bg = img; return new Promise((res, rej) => { img.onload = res; img.onerror = rej; }); } let general_solo = false; let part_solo = false; const drum = { a_ctx: new AudioContext(), generate_sound: (part) => { // called each time we need to play a source const source = drum.a_ctx.createBufferSource(); source.buffer = part.buf; source.connect(drum.gain); // to keep only one playing at a time // simply store this sourceNode, and stop the previous one if(general_solo){ // stop all playing sources drum.parts.forEach(p => (p.source && p.source.stop(0))); } else if (part_solo && part.source) { // stop only the one of this part part.source.stop(0); } // store the source part.source = source; source.start(0); }, parts: [{ name: 'hihat', x: 90, y: 116, w: 160, h: 70, audio_src: 'kbgd2jm7ezk3u3x/hihat.mp3' }, { name: 'snare', x: 79, y: 192, w: 113, h: 58, audio_src: 'h2j6vm17r07jf03/snare.mp3' }, { name: 'kick', x: 80, y: 250, w: 200, h: 230, audio_src: '1cdwpm3gca9mlo0/kick.mp3' }, { name: 'tom', x: 290, y: 210, w: 110, h: 80, audio_src: 'h8pvqqol3ovyle8/tom.mp3' } ], bg_src: '0jkaeoxls18n3y5/_drumkit.jpg?dl=0', }; drum.gain = drum.a_ctx.createGain(); drum.gain.gain.value = .5; drum.gain.connect(drum.a_ctx.destination); function initCanvas() { const c = drum.canvas = document.createElement('canvas'); const ctx = drum.ctx = c.getContext('2d'); c.width = drum.bg.width; c.height = drum.bg.height; ctx.drawImage(drum.bg, 0, 0); document.body.appendChild(c); addEvents(c); } const isHover = (x, y) => (drum.parts.filter(p => (px < x && px + pw > x && py < y && py + ph > y))[0] || false); function addEvents(canvas) { let mouse_hovered = false; canvas.addEventListener('mousemove', e => { mouse_hovered = isHover(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop) if (mouse_hovered) { canvas.style.cursor = 'pointer'; } else { canvas.style.cursor = 'default'; } }) canvas.addEventListener('mousedown', e => { e.preventDefault(); if (mouse_hovered) { drum.generate_sound(mouse_hovered); } }); const checkboxes = document.querySelectorAll('input'); checkboxes[0].onchange = function() { general_solo = this.checked; general_solo && (checkboxes[1].checked = part_solo = true); }; checkboxes[1].onchange = function() { part_solo = this.checked; !part_solo && (checkboxes[0].checked = general_solo = false); }; } Promise.all([initAudios(), initImages()]) .then(initCanvas); })() /* Audio Samples are from https://sampleswap.org/filebrowser-new.php?d=DRUMS+%28FULL+KITS%29%2FSpasm+Kit%2F Original image is from http://truimg.toysrus.co.uk/product/images/UK/0023095_CF0001.jpg?resize=500:500 */ 
 <label>general solo<input type="checkbox"></label><br> <label>part solo<input type="checkbox"></label><br> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM