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..
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}, {...}, ...];