2017-07-27 12 views
1

私はjavascriptでビジュアライザを作っています。音楽ディレクトリを選択すると、そのディレクトリ内のファイルを選択して再生し、ビジュアライザを移動させることができます。しかし、ディレクトリを読み込んだ後に曲を4回以上変更すると、ビジュアライザーからの反応が遅くなります。なぜこれが起こっているのか分かりません。 この問題の例を挙げてください。メモリリークなどありますか?

低速になるまで、ドロップダウンボックスから曲を変更し続けます。 yuriy636で指摘のように

window.onload = function() { 
 
    var input = document.getElementById("file"); 
 
    var audio = document.getElementById("audio"); 
 
    var selectLabel = document.querySelector("label[for=select]"); 
 
    var audioLabel = document.querySelector("label[for=audio]"); 
 
    var select = document.querySelector("select"); 
 
    var context = void 0, 
 
    src = void 0, 
 
    res = [], 
 
    url = ""; 
 

 
    function processDirectoryUpload(event) { 
 
    var webkitResult = []; 
 
    var mozResult = []; 
 
    var files; 
 
    console.log(event); 
 
    select.innerHTML = ""; 
 

 
    // do mozilla stuff 
 
    function mozReadDirectories(entries, path) { 
 
     console.log("dir", entries, path); 
 
     return [].reduce.call(entries, function(promise, entry) { 
 
      return promise.then(function() { 
 
      return Promise.resolve(entry.getFilesAndDirectories() || entry) 
 
       .then(function(dir) { 
 
       return dir 
 
       }) 
 
      }) 
 
     }, Promise.resolve()) 
 
     .then(function(items) { 
 
      var dir = items.filter(function(folder) { 
 
      return folder instanceof Directory 
 
      }); 
 
      var files = items.filter(function(file) { 
 
      return file instanceof File 
 
      }); 
 
      if (files.length) { 
 
      // console.log("files:", files, path); 
 
      mozResult = mozResult.concat.apply(mozResult, files); 
 
      } 
 
      if (dir.length) { 
 
      // console.log(dir, dir[0] instanceof Directory); 
 
      return mozReadDirectories(dir, dir[0].path || path); 
 

 
      } else { 
 
      if (!dir.length) { 
 
       return Promise.resolve(mozResult).then(function(complete) { 
 
       return complete 
 
       }) 
 
      } 
 
      } 
 

 
     }) 
 

 
    }; 
 

 
    function handleEntries(entry) { 
 
     let file = "webkitGetAsEntry" in entry ? entry.webkitGetAsEntry() : entry 
 
     return Promise.resolve(file); 
 
    } 
 

 
    function handleFile(entry) { 
 
     return new Promise(function(resolve) { 
 
     if (entry.isFile) { 
 
      entry.file(function(file) { 
 
      listFile(file, entry.fullPath).then(resolve) 
 
      }) 
 
     } else if (entry.isDirectory) { 
 
      var reader = entry.createReader(); 
 
      reader.readEntries(webkitReadDirectories.bind(null, entry, handleFile, resolve)) 
 
     } else { 
 
      var entries = [entry]; 
 
      return entries.reduce(function(promise, file) { 
 
       return promise.then(function() { 
 
       return listDirectory(file) 
 
       }) 
 
      }, Promise.resolve()) 
 
      .then(function() { 
 
       return Promise.all(entries.map(function(file) { 
 
       return listFile(file) 
 
       })).then(resolve) 
 
      }) 
 
     } 
 
     }) 
 

 
     function webkitReadDirectories(entry, callback, resolve, entries) { 
 
     console.log(entries); 
 
     return listDirectory(entry).then(function(currentDirectory) { 
 
      console.log(`iterating ${currentDirectory.name} directory`, entry); 
 
      return entries.reduce(function(promise, directory) { 
 
      return promise.then(function() { 
 
       return callback(directory) 
 
      }); 
 
      }, Promise.resolve()) 
 
     }).then(resolve); 
 
     } 
 

 
    } 
 

 
    function listDirectory(entry) { 
 
     console.log(entry); 
 
     return Promise.resolve(entry); 
 
    } 
 

 
    function listFile(file, path) { 
 
     path = path || file.webkitRelativePath || "/" + file.name; 
 
     console.log(`reading ${file.name}, size: ${file.size}, path:${path}`); 
 
     webkitResult.push(file); 
 
     return Promise.resolve(webkitResult) 
 
    }; 
 

 
    function processFiles(files) { 
 
     Promise.all([].map.call(files, function(file, index) { 
 
      return handleEntries(file, index).then(handleFile) 
 
     })) 
 
     .then(function() { 
 
      console.log("complete", webkitResult); 
 
      res = webkitResult; 
 
      res.reduce(function(promise, track) { 
 
      return promise.then(function() { 
 
       return playMusic(track) 
 
      }) 
 
      }, displayFiles(res)) 
 
     }) 
 
     .catch(function(err) { 
 
      alert(err.message); 
 
     }) 
 
    } 
 

 
    if ("getFilesAndDirectories" in event.target) { 
 
     return (event.type === "drop" ? event.dataTransfer : event.target).getFilesAndDirectories() 
 
     .then(function(dir) { 
 
      if (dir[0] instanceof Directory) { 
 
      console.log(dir) 
 
      return mozReadDirectories(dir, dir[0].path || path) 
 
       .then(function(complete) { 
 
       console.log("complete:", webkitResult); 
 
       event.target.value = null; 
 
       }); 
 
      } else { 
 
      if (dir[0] instanceof File && dir[0].size > 0) { 
 
       return Promise.resolve(dir) 
 
       .then(function() { 
 
        console.log("complete:", mozResult); 
 
        res = mozResult; 
 
        res.reduce(function(promise, track) { 
 
        return promise.then(function() { 
 
         return playMusic(track) 
 
        }) 
 
        }, displayFiles(res)) 
 
       }) 
 
      } else { 
 
       if (dir[0].size == 0) { 
 
       throw new Error("could not process '" + dir[0].name + "' directory" + " at drop event at firefox, upload folders at 'Choose folder...' input"); 
 
       } 
 
      } 
 
      } 
 
     }).catch(function(err) { 
 
      alert(err) 
 
     }) 
 
    } 
 

 
    files = event.target.files; 
 

 
    if (files) { 
 
     processFiles(files) 
 
    } 
 

 
    } 
 

 
    function displayFiles(files) { 
 
    select.innerHTML = ""; 
 
    return Promise.all(files.map(function(file, index) { 
 
     return new Promise(function(resolve) { 
 
     if (/^audio/.test(file.type)) { /* do stuff, that is all code currently within Promise resolver function */ } else { /* proceed to next file */ 
 
      resolve() 
 
     } 
 
     var option = new Option(file.name, index); 
 
     select.appendChild(option); 
 
     resolve() 
 
     }) 
 
    })) 
 
    } 
 

 
    function handleSelectedSong(event) { 
 
    if (res.length) { 
 
     var index = select.value; 
 
     var track = res[index]; 
 
     playMusic(track) 
 
     .then(function(filename) { 
 
      console.log(filename + " playback completed") 
 
     }) 
 
    } else { 
 
     console.log("No songs to play") 
 
    } 
 
    } 
 

 
    function playMusic(file) { 
 
    return new Promise(function(resolve) { 
 
     audio.pause(); 
 
     audio.onended = function() { 
 
     audio.onended = null; 
 
     if (url) URL.revokeObjectURL(url); 
 
     resolve(file.name); 
 
     } 
 
     if (url) URL.revokeObjectURL(url); 
 
     url = URL.createObjectURL(file); 
 
     audio.load(); 
 
     audio.src = url; 
 
     audio.play(); 
 
     audioLabel.textContent = file.name; 
 
     context = context || new AudioContext(); 
 
     src = src || context.createMediaElementSource(audio); 
 
     src.disconnect(context); 
 

 
     var analyser = context.createAnalyser(); 
 

 
     var canvas = document.getElementById("canvas"); 
 
     canvas.width = window.innerWidth; 
 
     canvas.height = window.innerHeight; 
 
     var ctx = canvas.getContext("2d"); 
 

 
     src.connect(analyser); 
 
     analyser.connect(context.destination); 
 

 
     analyser.fftSize = 16384; 
 

 
     var bufferLength = analyser.frequencyBinCount; 
 
     console.log(bufferLength); 
 

 
     var dataArray = new Uint8Array(bufferLength); 
 
     var WIDTH = canvas.width; 
 
     var HEIGHT = canvas.height; 
 

 
     var barWidth = (WIDTH/bufferLength) * 32; 
 
     var barHeight; 
 
     var x = 0; 
 

 
     function renderFrame() { 
 
     requestAnimationFrame(renderFrame); 
 
     x = 0; 
 

 
     analyser.getByteFrequencyData(dataArray); 
 

 
     ctx.fillStyle = "#1b1b1b"; 
 
     ctx.fillRect(0, 0, WIDTH, HEIGHT); 
 

 
     for (var i = 0; i < bufferLength; i++) { 
 
      barHeight = dataArray[i]; 
 

 

 
      ctx.fillStyle = "rgb(5,155,45)" 
 
      ctx.fillRect(x, (((HEIGHT - barHeight - 5 % barHeight) + (20 % HEIGHT - barHeight))), barWidth, barHeight + 20 % HEIGHT); 
 

 
      x += barWidth + 2; 
 
     } 
 
     } 
 

 
     renderFrame(); 
 
    }) 
 

 
    } 
 

 
    input.addEventListener("change", processDirectoryUpload); 
 
    select.addEventListener("change", handleSelectedSong); 
 
}
<canvas id="canvas" width="window.innerWidth" height="window.innerHeight"></canvas> 
 
<div id="content"> 
 
    <label class="custom-file-upload"> 
 
    Select Music directory <input id="file" type="file" accept="audio/*" directory allowdirs webkitdirectory/> 
 
    <p style="color: rgb(5,195,5);">Now playing:<label for="audio"></label></p> 
 

 
    <p style="color: rgb(5,195,5);">Select Song</p> 
 
    <select id="select"> 
 
    </select> 
 
    <audio id="audio" controls></audio>

+0

私はそれがなぜ起こるか伝えるためにJavaScriptのパフォーマンスを分析するには経験していないんだけど、私はいくつかのプロファイリング(デベロッパーツールのパフォーマンスタブ)行なったし、それが歌はブラウザを過ごすたびに変更した後のように見えますアイドルよりもずっと時間がかかるスクリプト:https://pastebin.com/RV97UraDそれがあなたを助けてくれることを願っています。 – yuriy636

+0

ええ、私もそれに気付いています。 – Nickh90

答えて

2

、あなたは以前のものを停止することはありませんせずに、すべての新しい歌のための新しいアニメーションを開始しています。 5曲を演奏しても、すべてのフレームで5つのビジュアライゼーションループが実行され、5つのアナライザがあります。

ここで行うのがベストがあなたのコードをリファクタリングすることです:

  • 最初のロード時に一度、それを宣言し、ストリームは
  • それをフィード更新のみがキャンバスのアニメーションが自律的にする、単一アナライザを作成します<canvas>が1つだけであるため、レンダリングアニメーションを1つだけ開始してください。

単一のアナライザを使用する場合、レンダリングでは新しいソース、それは常に同じキャンバス、同じアナライザ、同じ視覚化です。

ここでは概念の簡単な証明ですが、本当に汚れていますが、私はあなたが何をしたのか、その理由を理解できることを願っています。

window.onload = function() { 
 
    var input = document.getElementById("file"); 
 
    var audio = document.getElementById("audio"); 
 
    var selectLabel = document.querySelector("label[for=select]"); 
 
    var audioLabel = document.querySelector("label[for=audio]"); 
 
    var select = document.querySelector("select"); 
 

 

 
    var viz = null; 
 

 
    // removed all the IDK what it was meant for directory special handlers 
 

 
    function displayFiles() { 
 
    select.innerHTML = ""; 
 
    // that's all synchronous, why Promises ? 
 
    res = Array.prototype.slice.call(input.files); 
 
    res.forEach(function(file, index) { 
 
     if (/^audio/.test(file.type)) { 
 
     var option = new Option(file.name, index); 
 
     select.appendChild(option); 
 
     } 
 
    }); 
 

 
    if (res.length) { 
 
     var analyser = initAudioAnalyser(); 
 
     viz = initVisualization(analyser); 
 
     // pre-select the first song ? 
 
     handleSelectedSong(); 
 
     audio.pause(); 
 
    } 
 
    } 
 

 
    function handleSelectedSong(event) { 
 
    if (res.length) { 
 
     var index = select.value; 
 
     var track = res[index]; 
 
     playMusic(track) 
 
     .then(function(filename) { 
 
      console.log(filename + " playback completed") 
 
     }) 
 
     viz.play(); 
 
    } else { 
 
     console.log("No songs to play") 
 
    } 
 
    } 
 

 
    function playMusic(file) { 
 
    return new Promise(function(resolve) { 
 
     var url = audio.src; 
 
     audio.pause(); 
 
     audio.onended = function() { 
 
     audio.onended = null; 
 
     // arguablily useless here since blobURIs are just pointers to real file on the user's system 
 
     if (url) URL.revokeObjectURL(url); 
 
     resolve(file.name); 
 
     } 
 

 
     if (url) URL.revokeObjectURL(url); 
 
     url = URL.createObjectURL(file); 
 
     //  audio.load(); // would just set a 404 since you revoked the URL just before 
 
     audio.src = url; 
 
     audio.play(); 
 
     audioLabel.textContent = file.name; 
 

 
    }); 
 
    } 
 

 
    function initAudioAnalyser() { 
 
    var context = new AudioContext(); 
 
    var analyser = context.createAnalyser(); 
 
    analyser.fftSize = 16384; 
 

 
    var src = context.createMediaElementSource(audio); 
 
    src.connect(analyser); 
 
    src.connect(context.destination); 
 

 
    return analyser; 
 

 
    } 
 

 
    function initVisualization(analyser) { 
 

 
    var canvas = document.getElementById("canvas"); 
 
    canvas.width = window.innerWidth; 
 
    canvas.height = window.innerHeight; 
 
    var ctx = canvas.getContext("2d"); 
 

 
    var bufferLength = analyser.frequencyBinCount; 
 

 
    var dataArray = new Uint8Array(bufferLength); 
 
    var WIDTH = canvas.width; 
 
    var HEIGHT = canvas.height; 
 

 
    var barWidth = (WIDTH/bufferLength) * 32; 
 
    var barHeight; 
 
    var x = 0; 
 

 
    var paused = true; 
 
    
 
    function renderFrame() { 
 
     if (!paused) { 
 
     requestAnimationFrame(renderFrame); 
 
     } else { 
 
     return; 
 
     } 
 
     x = 0; 
 

 
     analyser.getByteFrequencyData(dataArray); 
 

 
     ctx.fillStyle = "#1b1b1b"; 
 
     ctx.fillRect(0, 0, WIDTH, HEIGHT); 
 

 
     ctx.fillStyle = "rgb(5,155,45)" 
 
     ctx.beginPath(); 
 
     for (var i = 0; i < bufferLength; i++) { 
 
     barHeight = dataArray[i]; 
 
     // micro-optimisation, but concatenating all the rects in a single shape is easier for the CPU 
 
     ctx.rect(x, (((HEIGHT - barHeight - 5 % barHeight) + (20 % HEIGHT - barHeight))), barWidth, barHeight + 20 % HEIGHT); 
 
     x += barWidth + 2; 
 
     } 
 
     ctx.fill(); 
 
    } 
 
    var viz = window.viz = { 
 
     play: function() { 
 
     if(paused){ 
 
      paused = false; 
 
      renderFrame(); 
 
      } 
 
     }, 
 
     pause: function() { 
 
     paused = true; 
 
     clearTimeout(pauseTimeout); 
 
     pauseTimeout = null; 
 
     }, 
 
    }; 
 
    // we can even add auto pause linked to the audio element 
 
    var pauseTimeout = null; 
 
    audio.onpause = function() { 
 
     // let's really do it in 2s to keep the tear down effect 
 
     pauseTimeout = setTimeout(viz.pause, 2000); 
 
    } 
 
    audio.onplaying = function() { 
 
     clearTimeout(pauseTimeout); 
 
     // we were not playing 
 
     if(!pauseTimeout){ 
 
     viz.play(); 
 
     } 
 
    } 
 
    return viz; 
 
    } 
 

 
    input.addEventListener("change", displayFiles); 
 
    select.addEventListener("change", handleSelectedSong); 
 
}
<canvas id="canvas" width="window.innerWidth" height="window.innerHeight"></canvas> 
 
<div id="content"> 
 
    <label class="custom-file-upload"> 
 
    Select Music directory <input id="file" type="file" accept="audio/*" directory allowdirs webkitdirectory/> 
 
    <p style="color: rgb(5,195,5);">Now playing:<label for="audio"></label></p> 
 

 
    <p style="color: rgb(5,195,5);">Select Song</p> 
 
    <select id="select"> 
 
    </select> 
 
    <audio id="audio" controls></audio>

+0

私はあなたが言ったことを誤って解釈しました。「単一のアナライザを使用する場合、ソースを変更すると新しいレンダリングは使用されません。常に同じキャンバス、同じアナライザ、同じ視覚化です。曲が選択されたキャンバス上に常に同じ外観が表示されます。混乱しました。誰でもこれが間違いなく機能します。 – Nickh90

+0

しかし、私は少しの問題に気づいた。 2秒間が経過する前に早く再生/一時停止を押すと、毎回減速する前と同じことが始まります。もう一度ポーズして2秒待って修正することができます。 – Nickh90

+0

@ニックヒューイットあなたは正しい、私が何をしたのか分からない。正当な理由で元の問題を再導入しました...今すぐ修正する必要があります。 – Kaiido

関連する問題