Cristina Jadi Martins
Cristina Jadi Martins

Reputation: 26

How to get value from a template rendered several times with Meteor?

I want to get different durations of audios inside a helper, from a template that is rendered several times. Is there any way to do that other than inserting the audio duration in the database? I've tried a lot of stuff, some not recommended at all, and none of it works. The ones that show me anything, show me only the duration of the first audio. Here is one of the uglier jerryrigs that actually return a number, but it's still pretty broken and causes a setInterval that doesn't stop.

Meteor.myaudioplayer.helpers({
   audioduration: function(){
     //if using ReactiveVar...
    audio = $("audio").get(0);
    instance = Template.instance();

        if (!instance.audioduration.get() || isNaN(parseInt(audio.duration))) {
              audioLenght = Meteor.setInterval(function() {
                    var totaltime = parseInt(audio.duration, 10);
                    var mins = Math.floor(audio.duration / 60, 10);
                    var secs = totaltime - mins * 60;
                    var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);

                    instance.audioduration.set(gimmethetime);
                    return gimmethetime;

              }, 500);
        } 

        else {
            Meteor.clearInterval(audioLenght);
            return instance.audioduration.get();
        }
      }
});

This is my HTML if it somehow helps.

<div class="myplayer {{label}}>
    
    <div class=" gutter ">
	  <div class="loading">		
	  </div>
	  <div class="played"> 
	  </div>	
	  <div class="handle"> 
	  </div>
   </div>

	<div class="controls ">
	  <span class="playtoggle glyphicon glyphicon-play "></span>
		<div class="volume-control ">
		  <span class="volume glyphicon glyphicon-volume-up "></span>

			<div class="volume-gutter hidden ">
			  <div class="volume-meter ">
				 <div class="volume-handle "></div>
			  </div>
			</div>

	       </div>
			
    <span class="title">{{audioName}}</span>
    <span class="timeleft pull-right"> {{audioplayed}} / {{audioduration}}</span>
    </div>
 </div>

<audio class={{label}}>
  <source src={{mysource}} format={{myformat}}/>
</audio>

It's also important to get the current audio position and and audio duration in other parts of the player, but as those are inside events triggered by the user, they are less problematic, since I can ask the browser what the elements look like using the event target as a parameter.

EDIT Someone asked for my code on the "play" button. Here it is. I'm still on the way to adjusting the variables so I can have two play functions at the same time, but works just fine. NOTE: I changed the HTML above to match this code, since I messed with some class names between versions.

"click .playtoggle": function(event) {
  thisplayer = $(event.target).parents(".audioplayer").attr("id");
  thisaudio = $("#" + thisplayer).find("audio").get(0);

  console.log(thisaudio.duration);

  //Playing
  if (!playing) {
    playing = true;
    $("#" + thisplayer).find(".playtoggle").removeClass("glyphicon-play");
    $("#" + thisplayer).find(".playtoggle").addClass("glyphicon-pause");
    thisaudio.play();

    playIt = Meteor.setInterval(function() {
      //vars for the visual aspect
      var handlepadding = parseInt($("#" + thisplayer).find(".handle").css("width")) / 2;
      var gutterwidth = $("#" + thisplayer).find(".gutter").width() - handlepadding;
      var soundcoordinates = parseInt(thisaudio.currentTime, 10);
      var totaltime = parseInt(thisaudio.duration, 10);
      var playedgutter = gutterwidth * soundcoordinates / totaltime;

      // If user is not messing with the handle, give the position
      if (!draghandle) {
        $("#" + thisplayer).find(".played").css("width", playedgutter + "px");
        $("#" + thisplayer).find(".handle").css("left", playedgutter + "px");
      }

      // If the audio has come to an end, acknowledge that it has ended
      if (soundcoordinates == totaltime) {
        playing = false;
        $("#" + thisplayer).find(".playtoggle").removeClass("glyphicon-pause");
        $("#" + thisplayer).find(".playtoggle").addClass("glyphicon-play");
        Meteor.clearInterval(playIt);

      }

      // Send the data to the timer
      instance.timeplayed.set(soundcoordinates);

    }, 500);

    return;
  }
  // END Playing

  // Pausing
  if (playing) {
    playing = false;
    $("#" + thisplayer).find(".playtoggle").removeClass("glyphicon-pause");
    $("#" + thisplayer).find(".playtoggle").addClass("glyphicon-play");
    Meteor.clearInterval(playIt);
    thisaudio.pause();
    return;
  }
//END pausing

}

Upvotes: 0

Views: 108

Answers (2)

Cristina Jadi Martins
Cristina Jadi Martins

Reputation: 26

I've found an answer to my particular problem. It's probably not the prettiest thing, and it wasn't exactly what I was looking for, since it doesn't use helpers anymore, but it works. I had to change the HTML a bit, so now, instead of two helpers inside the timer ".timeleft" span (one for progress and another for duration), I have two sets of spans.

<span class="timeleft pull-right">
 <span class="audioplayed"></span> /<span class="audioduration"></span>
</span>

Inside the onRendered, I have:

      audio = $("audio").get(0);

      idArray = $(".audioplayer").map(function() {
        return this.id;
      }).get();

      if (audio.duration && !isNaN(audio.duration)) {
        Meteor.clearInterval(getDuration);
      } else {
        getDuration = Meteor.setInterval(function() {
          for (i = 0; i < idArray.length; i++) {
            var audioduration = $("#" + idArray[i]).find("audio").get(0).duration;
            var totaltime = parseInt(audioduration, 10);
            var mins = Math.floor(audioduration / 60, 10);
            var secs = totaltime - mins * 60;
            var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);

            $("#" + idArray[i]).find(".audioduration").text(gimmethetime);
          }
          if (audio.duration && !isNaN(audio.duration)) {
            Meteor.clearInterval(getDuration);
          }
        }, 500);
      }

Let me explain what I did. Inside the onRendered the variable audio returns only the first audio file and it comes back as NaN because the element has not quite finished rendering yet. In order to get the two values, I created an array with the ids from each div.audioplayer that has been rendered (the idArray variable looked liked this: [label1, label2]). I then ran those ids in a for loop to get the audio durations and write then on the right element (the child of the div#label[i]).

However, this runs into the same problem of the audio variable: the template hasn't finished rendering, so when I ask my document how long are those audios, it doesn't know. My workaround was to put my loop in a setInterval, so that it keeps asking until it gets an answer, and then it stops. I can use the audio variable for validation, since when it gets back a value it is likely the other audios will too. Now, I have the two audio durations, each in it's corresponding timer for this template that was rendered twice.

Template of the audioplayer rendered twice

I intend to use the same technique to update the audio progress in the audioplayed portion of the timer.

Upvotes: 0

mutdmour
mutdmour

Reputation: 543

HTML

<span class="timeleft pull-right"> {{audioplayed}} / <span id="duration"></span></span>

js func

function  gimmethetime(audio){
  var totaltime = parseInt(audio.duration, 10);
  var mins = Math.floor(audio.duration / 60, 10);
  var secs = totaltime - mins * 60;
  var timeLeft = mins + ':' + (secs > 9 ? secs : '0' + secs);
  $("#duration").text(timeLeft);
}

Using jQuery to set the time at the start

Template.myaudioplayer.onRendered(
   function(){
        audio = $("audio").get(0);
        if (audio) {
             gimmethetime(audio);
        } 
      }
);

when user hits play

 ...
 playIt = Meteor.setInterval(function() {
    gimmethetime(thisaudio);
    ....
 }
 ...

Upvotes: 1

Related Questions