Implementing the data model

Now that we've designed the model, let's go ahead and implement it.

Getting ready

Like in our previous projects, we'll have two data models: the first one to deal with the collection of playable files, and the second one to deal with handling a specific playable file. The first, VoiceRecDocumentCollection.js is quite similar to our previous projects, and so we won't go over it in this task. But the VoiceRecDocument.js file is very different, so go ahead and open it up (it's in the www/models directory), so you can follow along.

Getting on with it

Let's start using the following code snippet:

DOC.VoiceRecDocument = function(theFileEntry, completion, failure)
{
  var self = this;
  self.fileEntry = theFileEntry;
  self.fileName = self.fileEntry.fullPath;
  self.fileType = PKUTIL.FILE.getFileExtensionPart(self.fileName);
  self.completion = completion;
  self.failure = failure;
  self.state = "";

As in our prior projects, the incoming parameters include a file entry obtained from the File API. We'll use this to determine the name of the audio file to play as well as its type. We're using some new functions, introduced in this version of the framework, to do work with the various portions that make up a file, namely, the path, the filename, and the file extension. Earlier, we use PKUTIL.FILE.getFileExtensionPart() to obtain the type of the file, whether it is an MP3, WAV, or something else.

  self.title = PKUTIL.FILE.getFileNamePart(self.fileName);
  self.media = null;
  self.position = 0;
  self.duration = 0;
  self.playing = false;
  self.recording = false;
  self.paused = false;
  self.positionTimer = -1;
  self.durationTimer = -1;

Here we define several properties that we will use to keep track of the various states and timers we need to use to properly manage our audio:

  • title: This property gives the title of the file, essentially the filename minus the path and extension.
  • media: This property gives the Media object provided by PhoneGap. This property will be set whenever the program needs to play a sound or record something.
  • position: This property gives the approximate position in the sound file for playback. It's approximate because it is updated every few milliseconds with the position. We'll discuss why in a bit.
  • duration: This property gives the duration of the sound file (if playing), or the approximate duration of the recording (while, or after, recording).
  • playing, recording, paused: These are simply Boolean variables intended to make it easy to determine what we're doing. Are we playing the file, recording a file, and, if we're playing, are we paused?
  • durationTimer, positionTimer: Timer IDs are used to track the intervals that get created whenever we load a media file or prepare one for recording. The durationTimer property updates the duration property, and the positionTimer property updates the position.
      self.getFileName = function()
      {
        return self.fileName;
      }
    
      self.setFileName = function(theFileName)
      {
        self.theFileName = theFileName;
        self.fileType = PKUTIL.FILE.getFileExtensionPart(self.fileName);
        self.title = PKUTIL.FILE.getFileNamePart(self.fileName);
      }

The preceding code snippet handles getting and setting the filename. If we set a filename, we have to update the filename, type, and title.

  self.initializeMediaObject = function()
  {

This method is a very important method; we'll be calling it at the top of most of our methods that work with playback or recording. This is to ensure that the media property is properly initialized. But it is also to ensure a few other details are correctly set up, as follows:

    if (self.media == null)
    {
      if (PKDEVICE.platform()=="android")
      {
        self.fileName = self.fileName.replace ("file://","");
      }

First, we do these steps if and only if we don't already have a media object at hand. If we do, there's no need to initialize it again.

Secondly, we check if we're running on Android. If we are, the file:// prefix that comes out of the File API will confuse the Media APIs, and so we remove it.

      self.media = new Media(self.fileName, self.dispatchSuccess, self.dispatchFailure);

Next, we initialize the media property with a new Media object. This object requires the filename of the audio file, and two functions: one for when various audio functions complete successfully (generally only when playback or recording has stopped), and another for when something goes wrong.

      self.positionTimer = setInterval(self.updatePosition, 250);
      self.durationTimer = setInterval(self.updateDuration, 250);
    }
  }

Finally, we also set up our two timers to update every quarter of a second. These times could be made faster or slower depending upon the granularity of updates you like, but 250 milliseconds seems to be enough.

  self.isPlaying = function()
  {
    return self.playing;
  }

  self.isRecording = function()
  {
    return self.recording;
  }

Of course, like any good model, we need to provide methods to indicate our state. Hence, isPlaying and isRecording are used in the preceding code snippet.

  self.updatePosition = function()
  {
    if (self.playing)
    {
      self.media.getCurrentPosition(function(position)
      {
        self.position = position;
      }, self.dispatchFailure);
    } else
    {
      if (self.recording)
      {
        self.position += 0.25;
      } else
      {
        self.position = 0;
      }
    }
  }

If you recall, this function is called continuously during playback and recording. If playing, we ask the Media API what the current position is, but we have to supply a callback method in order to actually find out what the position is. This should usually be called nearly instantly, but we can't guarantee it, so this is why we have encapsulated obtaining the position somewhat. We'll define a getPosition() method later that just looks at the position property instead of having to do the callback every time we want to know where we are in the audio file.

  self.updateDuration = function()
  {
    if (self.media.getDuration() > -1)
    {
      self.duration = self.media.getDuration();
      clearInterval(self.durationTimer);
      self.durationTimer = -1;
    } else
    {
      self.duration--;
      if (self.duration < -20)
      {
        self.duration = -1;
        clearInterval(self.durationTimer);
        self.durationTimer = -1;
      }
    }
  }

Obtaining the duration is even harder than obtaining the current position, mainly because it is quite possible that the Media API is streaming a file from the Internet rather than playing a local file. Therefore, the duration may take some time to obtain.

For as long as the duration timer is running, we'll ask the Media API if it has a duration for the file yet. If it doesn't, it'll return -1. If it does return a value greater than -1, we can stop the timer, since once we get a duration, it isn't likely to change.

There's no need to keep asking for the duration forever, especially if we can't determine the duration, so we use the negative numbers -1 to -20 of our duration property as a kind of timeout. We subtract one each time we fail to obtain a valid duration, and if we go below -20, we give up by stopping the timer.

  self.getPlaybackPosition = function()
  {
    return self.position;
  }

  self.setPlaybackPosition = function(newPosition)
  {
    self.position = newPosition;
    self.initializeMediaObject();
    self.media.seekTo(newPosition * 1000);
  }

Getting the playback position is now simple, we just return our own position property. But sometimes we need to change the current playback position. To do this, we use the seekTo() method of the Media API to adjust the position. For whatever reason, the position used in the seekTo() method is in milliseconds, while the position we obtain constantly with our timer is in seconds, hence the multiplication by 1000.

  self.getDuration = function()
  {
    return self.duration;
  }

  self.startPlayback = function()
  {
    self.initializeMediaObject();
    self.media.play();
    self.paused = false;
    self.recording = false;
    self.playing = true;
  }

  self.pausePlayback = function()
  {
    self.initializeMediaObject();
    self.media.pause();
    self.playing = false;
    self.paused = true;
    self.recording = false;
  }

Starting playback is actually very simple: once we initialize the object, we just call the play() method on it. Playback will start as soon as possible. We also set our state properties to indicate that we are playing.

Once playing, we can also pause easily: we just have to call the pause() method. We update our state to reflect that we are paused as well.

  self.releaseResources = function()
  {
    if (self.recording)
    {
      self.stopRecording();
    }
    if (self.positionTimer > -1)
    {
      clearInterval(self.positionTimer);
    }
    if (self.durationTimer > -1)
    {
      clearInterval(self.durationTimer);
    }
    self.durationTimer = -1;
    self.positionTimer = -1;
    self.media.release();
    self.media = null;
  }

Since media files can consume a lot of memory, whenever they aren't in use, they should be released from memory. When we release the file, we also need to stop the timers, if running).

  self.stopPlayback = function()
  {
    self.initializeMediaObject();
    self.media.stop();
    self.isPlaying = false;
    self.isPaused = false;
    self.isRecording = false;
  }

Stopping playback is quite simple: just call the stop() method instead of the pause() method. The difference between the two is that pausing playback allows a subsequent call to the play() method to resume immediately where we paused. Calling the stop() method will reset our position to zero, so the next play() method will start from the beginning.

  self.startRecording = function()
  {
    self.initializeMediaObject();
    self.media.startRecord();
    self.isPlaying = false;
    self.isPaused = false;
    self.isRecording = true;
  }

  self.stopRecording = function()
  {
    self.initializeMediaObject();
    self.media.stopRecord();
    self.isPlaying = false;
    self.isPaused = false;
    self.isRecording = false;
  }

Recording is similarly easy: we just call startRecord() or stopRecord(). There is no functionality for providing support for pausing in the middle of recording.

  self.dispatchFailure = function(e)
  {
    console.log("While " + self.State + ", encountered error: " + e.target.error.code);
    if (self.failure)
    {
      self.failure(e);
    }
  }

Our failure method is pretty simple. If an error occurs, we'll log it, and then call the failure method given when creating this object.

  self.dispatchSuccess = function()
  {
    if (self.completion)
    {
      self.completion();
    }
  }
}

The success function is even simpler: we just call the completion() method passed in when creating the object.

What did we do?

In this task, we created the data model for a specific audio file as well as the methods for initiating, pausing, and stopping playback, and those for initiating and stopping recording.

What else do I need to know?

The completion method is generally called at the end of playback, though it can be called for other reasons as well. In general, one would use this to clean up the media object, but if it is called when not expected, the result would be an abrupt stop of playback.

The other important issue is that each platform supports only certain media files for playback and even different ones for recording. Here's a short list:

Platform

Plays

Records

iOS

WAV

WAV

Android

MP3,WAV, 3GR

3GR

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.165.131