可能是内存泄漏或其他什么?

时间:2017-07-27 20:50:49

标签: javascript html google-chrome html5-audio

我已经在javascript中创建了一个可视化工具,当您选择音乐目录时,您可以选择该目录中的文件进行播放并让可视化工具移动到。但是在加载目录然后更改歌曲超过4次之后,它似乎会导致从可视化工具中响应较少的运动。我不确定为什么会这样。 这是这种情况的一个例子。

继续从下拉框中更改歌曲,直到您看到它开始变慢。



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);
}
&#13;
<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>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:2)

如yuriy636所述,您正在为每首新歌开始一个新动画,而不会停止前一首。 因此,当你播放5首歌曲时,你仍然有5个可视化渲染循环在每一帧运行,以及5个分析器。

这里最好的做法是重构你的代码:

  • 创建一个分析器,只更新哪个流提供它
  • 使画布动画自主,在首次加载时声明一次
  • 因为你只有一个<canvas>,所以只启动一个渲染动画

使用单个分析器时,渲染在更改源时不会使用任何新内容,它始终是相同的画布,相同的分析器,相同的可视化。

这是一个概念的快速证明,真的很脏,但我希望你能够做到我所做的和为什么。

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>