CFMobile Example – Record and playback audio using ColdFusion Splendor

In this post I am going to show how easy it is to record audio and play it back in a mobile application using ColdFusion Splendor. If you haven’t already, you can download it from Adobe Labs.

I have tried to keep the application simple. There are two buttons, Record and Play. When you click Record button, the recording starts and the Stop button is displayed. Speak into the phone microphone to record your voice. When done, click Stop button. You can play back the audio by clicking Play button. You can also stop playback any time by clicking Stop button.

Here are the screen shots –

2013_03_18_screen1 2014_03_18_screen2

To keep the code simple in this post, I am going to focus mainly on recording and playback of audio. The actual application also checks if the recorded file is in temporary or persistent file system. If it is in the temporary file systems, then it copies it to the persistent file system. I have already explained how to move files from temporary to persistent file system using CFClient file APIs in my post – CFMobile Example – Taking picture and uploading to ColdFusion server.

When recording audio, cfclient framework first tries to find current file system (default set to persistent) and saves recorded audio file in that file system. However iOS seems to always record audio in temporary file system, irrespective of current file system set. So depending on where you run this demo application, you will see different messages.

Let’s start with HTML and JS scripts –

<!DOCTYPE html>
<meta HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

<script src="jquery-2.0.3.min.js" ></script>

<script>
	$(document).ready(function(){
		$("#recordBtn").prop("disabled",true);
		$("#playBtn").prop("disabled",true);
		$(document).on("click","#recordBtn", onRecordBtnClicked);
		$(document).on("click","#playBtn", onPlayBtnClicked);
	});

	function onRecordBtnClicked()
	{
		var btnTxt = $("#recordBtn").prop("value");
		if (btnTxt == "Record")
			startRecording();
		else
			stopRecording();
	}

	function onPlayBtnClicked()
	{
		var btnTxt = $("#playBtn").prop("value");
		if (btnTxt == "Play")
			playAudio();
		else
			stopAudio();
	}
</script>

<h1>CFMobile Audio Demo</h1>
Click on the Record buton and speak. When done, click the Stop button.<p>
You can playback recorded audio by clicking the Play button<p>

<input type="button" value="Record" id="recordBtn" style="margin-right:20px">
<input type="button" value="Play" id="playBtn">

In document ready event, I disable record and play buttons (I enable them when cfclient is ready) and register event handlers.

When record button is clicked (onRecordBtnClicked), I call startRecording or stopRecording functions depending on the label of the button. Similarly when play buton is clicked, I call playAudio or stopAudio. You will see all the four functions declared in cfclient block later.
After the script block I have HTML tags to display buttons.

cfclient block starts immediately after HTML code –

<cfclientsettings enabledeviceapi="true" >
<cfclient>
	<cfscript>
                //We will add cfclient code here
        </cfscript>
</cfclient>

We need to enable device APIs because this demo uses audio APIs. Any code below below cfclientsettings tag will be executed only after all device APIs are initialized. All code in this post will now go inside cfscript block.

$("##recordBtn").prop("disabled",false);
$("##playBtn").prop("disabled",false);

RECORDING_STARTED = 10;
MEDIA_STOPPED = 4;
MEDIA_PAUSED = 3;
MEDIA_RUNNING = 2;
PLAYBACK_STARTED = 11;
MEDIA_NONE = 0;

mediaFile = null;
mediaObj = null;
audioStatus = MEDIA_NONE;

First, I enable Record and Play buttons, because at this point all device APIs are initialized and we can call audio APIs.

Then I initialize a few constants for media status. Constants (values) MEDIA_STOPPED, MEDIA_PAUSED and MEDIA_RUNNING are defined by PhoneGap. Remaining constants are created by me for this demo.
I declare mediaFile variable to hold path of the recorded media file and mediaObj variable that points to PhoneGap media object. I initialize audio status to MEDIA_NONE.

function mediaStatusChanged (statusArg)
{
	switch (statusArg)
	{
		case 4: //MEDIA_STOPPED:
			$("##recordBtn").prop("disabled",false);
			$("##recordBtn").prop("value", "Record");
			$("##playBtn").prop("disabled",false);
			$("##playBtn").prop("value", "Play");
			audioStatus = MEDIA_NONE;
			break;
		//TODO: Handle pause media status
		case 11: //PLAYBACK_STARTED:
			$("##recordBtn").prop("disabled",true);
			$("##playBtn").prop("disabled",false);
			$("##playBtn").prop("value", "Stop");
			audioStatus = PLAYBACK_STARTED;
			break;
		case 10://RECORDING_STARTED:
			$("##recordBtn").prop("disabled",false);
			$("##recordBtn").prop("value", "Stop");
			$("##playBtn").prop("disabled",true);
			audioStatus = RECORDING_STARTED;
			break;
	}
}

mediaStatusChanged function is a hander function called when status of the media changes. This function just updates buttons status and sets new media status value.

function startRecording()
{
	try
	{
		var mediaFileName = "tempMedia.wav";

		mediaObj = cfclient.audio.createMedia(mediaFileName, function(statusArg){
			mediaStatusChanged(statusArg);
		});

		//For iOS, the media file would be created in the temporary file system
		//For Android, it will be created in the persistent file system

		//start recording
		cfclient.audio.record(mediaObj);
		audioStatus = RECORDING_STARTED;

		//update buttons
		mediaStatusChanged(RECORDING_STARTED);
	}
	catch (any e)
	{
		alert("Error recording - " + JSON.stringify(e));
	}
}

Recording is stored in a file with name “tempMedia.wav”. If file with the same name already exists, then it is overwritten. I create media object with this file name using cfclient API createMedia. The second argument is a handler function that is called when media status changes. In this function I call the handler function I created earlier – mediaStatusChanged.
Once the media object is created successfully, I call the record function of cfclient.

function stopRecording()
{
	if (isDefined("mediaObj"))
	{
		cfclient.audio.stopRecording(mediaObj);
	}
}

stopRecording function calls corresponding function of cfclient, if mediaObj is defined.

function playAudio ()
{
	if (isDefined("mediaObj"))
	{
		//workaround for Android crash issue. Create media object again
		mediaObj = cfclient.audio.createMedia(mediaObj.src, function(statusArg){
			mediaStatusChanged(statusArg);
		});

		cfclient.audio.play(mediaObj);
		mediaStatusChanged(PLAYBACK_STARTED);
	}
	else
		alert("No recording avilable to play");
}

playAudio function creates media object again from the file name. This is done to work around a bug in Android. You don’t need to do this in iOS (you can reuse the media object created for recording).
I call play method of cfclient to playback the audio and change the media and buttons status by calling mediaStatusChanged. When audio file is played till the end, MEDIA_STOPPED event is fired, which is handled by mediaStatusChanged function, which updates buttons status.

function stopAudio()
{
	cfclient.audio.stop(mediaObj);
}

When Stop button is clicked while media is playing, stopAudio function is called. This function calls stop method of cfclient. When media stops playing, MEDIA_STOPPED event is fired, which again is handled by mediaStatusChanged function.

Download project files and Android APK for this application. Note that index.cfm in this project (and application) has additional code to check if the audio file is in temporary file system and to move it to the persistent file system. Screen shots above are of application using this code. There is index2.cfm file this is not used in the packaged application, but it contains code described in this post (it does not contain file transfer code).

-Ram Kulkarni

Leave a Reply