Skip to content

Instantly share code, notes, and snippets.

@djvs
Last active August 29, 2015 14:27
Show Gist options
  • Save djvs/094c4c0990981031281d to your computer and use it in GitHub Desktop.
Save djvs/094c4c0990981031281d to your computer and use it in GitHub Desktop.
AngularJS multi track audio player (JS Audio API - depends on underscore.js, AngularJS, font-awesome)

An update on my earlier player ( https://gist.github.com/djvs/24006c975ff11fd889e3 ), with some fixes (removed Bootstrap/Glyphicons dependency, now depends only on Font-Awesome, underscore JS, Angular itself, and the attached images). And added multitrack functionality! Consider this an alternative to clunky browser-mandated HTML5 audio tag players, or alternative to JPlayer etc..

example

Javascript:

app.directive('audios',function($interval) {
    return {
        restrict:'A',
        scope: {
            audios: '='
        },
        templateUrl: '/partial/multiplayer',
        link: function($scope, element, attrs){
        },
        controller: function($scope){
            $scope.ctime = 0;
            $scope.duration = 0;
            $scope.plyr = {state: "paused", vol: 1.0};

            $scope.play = function(audio){
                var prevol;
                if(typeof $scope.audio != "undefined"){
                    $scope.audio.playing = false;
                    prevol = $scope.audio.volume;
                    $scope.audio.pause();
                    $scope.audio = false;
                } else {
                    prevol = 1.0;
                }
                _.each($scope.audios,function(a){
                    a.playing = false;
                });

                $scope.loadaudio(audio,prevol);

                $scope.plyr.state = "playing";
                $scope.audio.play();
            };

            $scope.loadaudio = function(audio,vol){
                audio.playing = true;
                $scope.audio = new Audio();
                $scope.audio.src = audio.url;
                $scope.audio.volume = vol;

                audioglob = $scope.audio;
                $scope.audio.addEventListener("ended", function(){
                    var aindex = $scope.audios.indexOf(audio);
                    if($scope.audios.length > (aindex + 1)){
                        $scope.play($scope.audios[aindex+1]);
                    }
                });

                $scope.unbind = $scope.$watch('audio.duration', function(newval){
                    var value = $scope.audio.duration.toFixed(1);
                    if(isNaN(value)){
                        $scope.duration = 0;
                    } else {
                        $scope.duration = value;
                    }
                });
                var ctimeint = $interval(function(){
                    var value = $scope.audio.currentTime.toFixed(1);
                    if(isNaN(value)){
                        $scope.ctime = 0;
                    } else {
                        $scope.ctime = value;
                    }
                },100);
            };

            $scope.loadaudio($scope.audios[0],1.0);

            $scope.resume = function(){
                if(typeof $scope.audio == "undefined"){
                  return false;
                }
                $scope.audio.play();
                $scope.plyr.state = "playing";
            };
            $scope.pause = function(){
                $scope.audio.pause();
                $scope.plyr.state = "paused";
            };
            $scope.changetime = function(t){
                if(typeof $scope.audio == "undefined"){
                  return false;
                }
                $scope.audio.currentTime = $scope.ctime;
            };
            $scope.changevol = function(t){
                if(typeof $scope.audio == "undefined"){
                  return false;
                }
                $scope.audio.volume = $scope.plyr.vol;
            };
            $scope.ntot = function(secs) {
              var hr  = Math.floor(secs / 3600);
              var min = Math.floor((secs - (hr * 3600))/60);
              var sec = Math.floor(secs - (hr * 3600) -  (min * 60));

              if (min < 10){
                min = "0" + min;
              }
              if (sec < 10){
                sec  = "0" + sec;
              }

              return min + ':' + sec;
            };
            $scope.$on('$destroy', function(){
              $scope.audio.pause();
              $scope.unbind();
              delete $scope.audio;
              if(typeof ctimeint != "undefined"){
                $interval.cancel(ctimeint);
              }
            });
        }
    };
});

View (at /partial/multiplayer) (uses tables for simplicity's sake, you could use a div with display:table etc. if you wanted)

<table class="audiowrap"><tr>
      <td class="ctrlbtn"> 
          <div class="playbtn" ng-if="plyr.state == 'paused'" ng-click="resume()" ng-class="{disabled: !audio}">▶</div>
          <div class="pausebtn" ng-else ng-click="pause()" ng-class="{disabled: !audio}">▌▌</div>
      </td> 
      <td class="volwrap" ng-class="{disabled: !audio}">
            <img src="/img/vol/0.png" ng-if="plyr.vol == 0.0">
            <img src="/img/vol/33.png" ng-elif="plyr.vol <= 0.33">
            <img src="/img/vol/66.png" ng-elif="plyr.vol <= 0.66">
            <img src="/img/vol/100.png" ng-else>
            <div class="wrap2">
              <input type="range" min="0.0" max="1.0" step="0.05" ng-model="plyr.vol" ng-change="changevol()" ng-disabled="!audio">
            </div>
      </td> 
      <td class="rangewrap" ng-class="{disabled:!audio}"> 
          <input type="range" min="0.0" max="{{ duration }}" step="0.1" ng-model="ctime" ng-change="changetime()" ng-disabled="!audio">
      </td>
      <td class="time"> 
          <span ng-if="audio && !isNaN(ctime) && !isNaN(duration)">{{ ntot(ctime) }} / {{ ntot(duration) }}</span>
          <span ng-else>(waiting for a song)</span>
      </td> 
</tr></table>
<audio style="display:none !important;" preload="auto" ng-if="!audio">
  <source ng-src="{{ audio.url }}" type="audio/mpeg">
</audio>
    

<div class="playlistwrap" ng-if="audios.length > 1">
    <div class="plitem" ng-repeat="aud in audios | orderBy:'weight'" ng-class="{playing: aud.playing}">
      <div class="downloadmp3" ng-if="aud.downloadable">
        <a ng-href="{{ aud.url }}" target="_blank" download="{{ aud.recording_file_name }}">
            <i class="fa fa-cloud-download"></i>
        </a>
      </div>
      <div class="audiohead" ng-click="play(aud)">
        <i ng-if="aud.playing" class="fa fa-play-circle nowplaying"></i>
        <span class="audioname">{{ aud.name }}</span>
      </div>
      <div class="audiocred">{{ aud.credit }}</div>
    </div>
</div>

CSS:

$border:rgba(192,192,192,1);
.audiowrap{
    margin:5px 0;
    border:solid 1px $border;
    border-radius:3px;
    width:100%;
    overflow:auto;
    background-color:white;
    line-height: 1.3em;
    table-layout:fixed;
    .disabled{
        opacity:0.5;
    }
    button{
        padding:0;
        border:0;
        background-color:white;
        outline:none;
    }
    td{
      padding-bottom:0 !important;
      text-align:center;
    }
    .ctrlbtn{
        border-right:solid 1px $border;
        color:#444;
        cursor:pointer;
        width:36px;
        padding:2px 3px;
        font-size:1.4em;
        text-align:center;
        .pausebtn{
            font-size:0.75em;
            margin-top:-3px;
        }
        .playbtn{
            margin-top:-4px;
        }
    }
    .time{
        background-color:white;
        color:black;
        text-align:center;
        font-size: 0.7em;
        padding: 3px 5px;
        font-family: helvetica, arial,sans-serif;
        margin-top:1px;
        width:110px;
    }
    .rangewrap, .volwrap{
        padding-top:3px !important;
    }
    .rangewrap{
        background-color:white;
        border-right:solid 1px $border;
        padding:0 6px;
        overflow:hidden;
        width:55%;
        input{
            width:100%;
        }
    }
    .volwrap{
        background-color:white;
        border-right:solid 1px $border;
        padding:0 6px;
        width:100px; 
        overflow:auto;
        .wrap2{
          overflow:hidden;
          input{
            width:73px;
          }
        }
        img{
          float:left;
          height:24px;
        }
    }
}

.playlistwrap{
    margin-bottom:12px;
    .plitem{
        overflow:auto;
        border-radius:3px;
        //color:black !important;
        //background-color:#f0f0f0;
        background-color:rgba(255,255,255,0.2);
        color:#cbc2b3;
        padding:2px;
        margin-bottom:4px;
        .downloadmp3{
            padding:0 4px;
/*            padding: 3px 16px;
            background-color:#e0e0e0;
            border:solid 1px #d0d0d0;
            font-size:1.1em; */
            float:right;
            *{
                color:black !important;
            }
        }
        .audiohead{
            padding:0 4px;
            display:block;
            overflow:hidden;
            cursor:pointer;
            .audioname{
                font-weight:bold;
            }
        }
        &.playing{
            background-color:rgba(255,255,255,0.8) !important;
            color:black !important;
        }
        .audiocred{
            display:block;
        }
        .nowplaying{
            color:#00C500;
        }
    }
}

Invocation:

<div audios="audios"></div>

for:

$scope.audios = [{name: "blah", url: "http://....", credit: "thanks to x y z, etc", downloadable: true}, {...}, ...];

0.png 0.png

33.png 33.png

66.png 66.png

100.png 100.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment