Mehdi Souregi
Mehdi Souregi

Reputation: 3265

Play wav byte data on the browser on a player

Can someone please help me on this issue I am a little confused, i am getting wav data from database, and I can manage to play this wav data on the browser using this function :

function playWave(byteArray) {
    var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    var myAudioBuffer = audioCtx.createBuffer(1, byteArray.length, 8000);
    var nowBuffering = myAudioBuffer.getChannelData(0);
    for (var i = 0; i < byteArray.length; i++) {
        nowBuffering[i] = byteArray[i];
    }

    var source = audioCtx.createBufferSource();
    source.buffer = myAudioBuffer;
    source.connect(audioCtx.destination);
    source.start();
}

Everything works fine, I only need a GUI player to play/pause/stop and eventually draw a spectrum.

First I tried to use the audio tag of HTML5 but you need to put a valid url in src paramater :

<audio controls="controls">
  Your browser does not support the &lt;audio&gt; tag. 
  <source src="../m/example.mp3" />
</audio> 

Is it possible to change the src parameter to something like a method where you can put and play your byte array? Is there any player that can handle this kind of situation? I just want to play a wav from database on a player (play/pause/stop) with a certain rate (8000Hz), it seems to be an easy issue, but I found no article or documentation talking about that on the internet. The only players I found on the internet , you need to provide a valid file.

Upvotes: 3

Views: 4658

Answers (2)

Fabian Schultz
Fabian Schultz

Reputation: 18556

You should be able to use Blob after converting the byteArray correctly. You can then create a blob Object URL and set that as src on the source element:

// Create blob from Uint8Array & Object URL.
const blob = new Blob([getByteArray()], { type: 'audio/wav' });
const url = URL.createObjectURL(blob);

// Get DOM elements.
const audio = document.getElementById('audio');
const source = document.getElementById('source');

// Insert blob object URL into audio element & play.
source.src = url;
audio.load();
audio.play();

// Get data from database/server, hardcoded here for simplicity.
function getByteArray() {
  const data = [82, 73, 70, 70, 222, 37, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1, 0, 68, 172, 0, 0, 136, 88, 1, 0, 2, 0, 16, 0, 100, 97, 116, 97, 186, 37, 0, 0, 0, 0, 255, 12, 2, 27, 254, 40, 2, 55, 254, 68, 1, 83, 0, 83, 0, 69, 0, 55, 255, 40, 2, 27, 253, 12, 3, 255, 254, 240, 0, 227, 1, 213, 255, 198, 1, 185, 255, 170, 1, 175, 255, 188, 1, 203, 255, 216, 1, 231, 255, 244, 2, 3, 254, 16];

  // Convert byteArray into Uint8Array.
  return new Uint8Array(data);
}
<audio controls="controls" id="audio" loop>
  Your browser does not support the &lt;audio&gt; tag. 
  <source id="source" src="" type="audio/wav" />
</audio>

ā˜ļøšŸ”Š This plays sound on click!

Upvotes: 6

Eduard Void
Eduard Void

Reputation: 2714

I made an angular controller for audio playback dialog here it is:

App.controller("PlaySoundDialogCtrl", function ($scope, $rootScope, $http, $interval, $mdDialog, $mdMedia, url) {

$scope.url = url;
$scope.error = "";
$scope.audioData = null;
$scope.resampleRate = 44100;
$scope.playing = false;
$scope.source = null;
$scope.buffer = null;
$scope.startedAt = 0;
$scope.pausedAt = 0;
$scope.duration = 0;
$scope.currentTime = 0;
$scope.timeChunks = [];
window.AudioContext = window.AudioContext ||
    window.webkitAudioContext ||
    window.mozAudioContext ||
    window.oAudioContext ||
    window.msAudioContext;
$scope.audioCtx = new AudioContext();

$http.get($scope.url, {responseType: "arraybuffer"}).
      success(function(data) {
        $scope.audioData = data;
        $scope.playByteArray(data);
      }).
      error(function(data, status) {
        $scope.error = "errors.sound-error";
      });

$scope.playByteArray = function(byteArray) {
  var data = new DataView(byteArray);
  var audio = new Int16Array(data.byteLength / Int16Array.BYTES_PER_ELEMENT);
  var len = audio.length;
  for(var jj = 0; jj < len; ++jj) {
    audio[jj] = data.getInt16(jj * Int16Array.BYTES_PER_ELEMENT, true);
  }

  var mono = new Float32Array(audio.length);
  var channelCounter = 0;
  for(var i = 0; i < audio.length; ) {
    mono[channelCounter] = (audio[i] > 0 ? audio[i]/32767 : audio[i]/-32768);
    i = i+1;
    channelCounter++;
  }

  $scope.buffer = $scope.audioCtx.createBuffer(1, mono.length, $scope.resampleRate);
  $scope.buffer.getChannelData(0).set(mono);
  $scope.duration = $scope.buffer.duration;
  for(var i = 0; i < 20; i++) {
    $scope.timeChunks.push(($scope.duration/20)*i);
  }
  window.setTimeout(function() {$scope.play();}, 100);
}

$scope.play = function() {
  var offset = $scope.pausedAt;

  $scope.source = $scope.audioCtx.createBufferSource();
  $scope.source.connect($scope.audioCtx.destination);
  $scope.source.buffer = $scope.buffer;
  $scope.source.start(0, offset);
  $scope.startedAt = $scope.audioCtx.currentTime - offset;
  $scope.pausedAt = 0;
  $scope.playing = true;
  $scope.playInterval = $interval(function() {
    $scope.currentTime = $scope.getCurrentTime();
    if($scope.currentTime > $scope.duration) {
      $scope.stop();
      $interval.cancel($scope.playInterval);
      $scope.currentTime = 0;
    }
  }, 500);
}

$scope.timeWatch = $scope.$watch("currentTime", function() {
  var all = $(".timeRanger").innerWidth();
  var leftOffset = Math.floor(((all-30)/$scope.duration)*$scope.currentTime);
  if($(".playerTooltip").length) {
    $(".playerTooltip")[0].style.setProperty("left", leftOffset+"px", "important");
  }
});

$scope.stop = function() {
  if($scope.source != null) {
    $scope.source.disconnect();
    $scope.source.stop(0);
    $scope.source = null;
  }
  $scope.pausedAt = 0;
  $scope.startedAt = 0;
  $scope.playing = false;
  if(angular.isDefined($scope.playInterval)) $interval.cancel($scope.playInterval);
}

$scope.pause = function() {
  var elapsed = $scope.audioCtx.currentTime - $scope.startedAt;
  $scope.stop();
  $scope.pausedAt = elapsed;
}

$scope.jump = function() {
  $scope.pause();
  $scope.pausedAt = $scope.currentTime;
  $scope.play();
}

$scope.getCurrentTime = function() {
    if($scope.pausedAt) {
        return $scope.pausedAt;
    }
    if($scope.startedAt) {
        return $scope.audioCtx.currentTime - $scope.startedAt;
    }
    return 0;
}

$scope.cancel = function() {
    $scope.stop();
    $mdDialog.cancel();
}

$scope.stopInterval = function() {
  if(angular.isDefined($scope.playInterval)) $interval.cancel($scope.playInterval);
}

$scope.$on('$destroy',function() {
  if(angular.isDefined($scope.playInterval)) $interval.cancel($scope.playInterval);
  if(angular.isDefined($scope.timeWatch)) $scope.timeWatch();
});

}).filter('secToTime', function() {
  return function(item) {
    var minutes = Math.floor(item/60);
    item -= minutes*60;
    item = Math.floor(item);
    if(item < 10) item = "0"+item;
    return minutes+":"+item;
  };
});

and the view part:

<div class="row text-center">
      {{currentTime | secToTime}}
        <button type="button" class="btn btn-primary" ng-click="pause()" ng-show="playing">Pause</button>
        <button type="button" class="btn btn-primary" ng-click="play()" ng-show="!playing">Play</button><br />
        <div class="timeRanger">
          <input type="range" min="0" max="{{duration}}" step="1" ng-mousedown="stopInterval()" ng-mouseup="jump()" ng-model="currentTime" />
          <div class="tooltip playerTooltip top fade in" tooltip-animation-class="fade" style="top: -33px; left: 0;">
            <div class="tooltip-arrow"></div>
            <div class="tooltip-inner">{{currentTime | secToTime}}</div>
          </div>
        </div>

        <!--<button type="button" class="btn btn-primary" ng-click="jump(t)" ng-repeat="t in timeChunks">{{t | secToTime}}</button>-->
      </div>

Upvotes: -1

Related Questions