diff --git a/SoundRecorder.iml b/SoundRecorder.iml
index 40b8fc4a..5783e78f 100644
--- a/SoundRecorder.iml
+++ b/SoundRecorder.iml
@@ -13,7 +13,7 @@
-
+
\ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
index 1cdca55a..98cc522d 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -62,13 +62,6 @@
-
-
-
-
-
-
-
@@ -76,32 +69,25 @@
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -110,11 +96,13 @@
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index ae3d3494..b4ad9869 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -28,4 +28,5 @@ dependencies {
compile 'com.android.support:recyclerview-v7:21.0.+'
compile 'com.melnykov:floatingactionbutton:1.1.0'
compile 'com.jpardogo.materialtabstrip:library:1.0.6'
+ compile 'com.googlecode.mp4parser:isoparser:1.0.2'
}
diff --git a/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java b/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java
index 6f0bb7a3..0efd383b 100644
--- a/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java
+++ b/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java
@@ -6,9 +6,12 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
+import android.util.Log;
import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener;
+import java.io.File;
+import java.util.ArrayList;
import java.util.Comparator;
/**
@@ -24,6 +27,9 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "saved_recordings.db";
private static final int DATABASE_VERSION = 1;
+
+
+
public static abstract class DBHelperItem implements BaseColumns {
public static final String TABLE_NAME = "saved_recordings";
@@ -33,6 +39,14 @@ public static abstract class DBHelperItem implements BaseColumns {
public static final String COLUMN_NAME_TIME_ADDED = "time_added";
}
+ /**
+ * The class contains the info of table two.
+ */
+ public static abstract class DBHelperItem2 implements BaseColumns {
+ public static final String TABLE_NAME = "saved_paused_recordings";
+
+ }
+
private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
@@ -43,17 +57,32 @@ public static abstract class DBHelperItem implements BaseColumns {
DBHelperItem.COLUMN_NAME_RECORDING_LENGTH + " INTEGER " + COMMA_SEP +
DBHelperItem.COLUMN_NAME_TIME_ADDED + " INTEGER " + ")";
+ /**
+ * Table two SQL -create string
+ */
+ private static final String SQL_CREATE_ENTRIES2 =
+ "CREATE TABLE " + DBHelperItem2.TABLE_NAME + " (" +
+ DBHelperItem2._ID + " INTEGER PRIMARY KEY" + COMMA_SEP +
+ DBHelperItem.COLUMN_NAME_RECORDING_NAME + TEXT_TYPE + COMMA_SEP +
+ DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH + TEXT_TYPE + COMMA_SEP +
+ DBHelperItem.COLUMN_NAME_RECORDING_LENGTH + " INTEGER " + COMMA_SEP +
+ DBHelperItem.COLUMN_NAME_TIME_ADDED + " INTEGER " + ")";
+
+
@SuppressWarnings("unused")
private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + DBHelperItem.TABLE_NAME;
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
+ db.execSQL(SQL_CREATE_ENTRIES2);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-
+ db.execSQL("DROP TABLE IF IT EXISTS "+ DBHelperItem.TABLE_NAME);
+ db.execSQL("DROP TABLE IF IT EXISTS "+ DBHelperItem2.TABLE_NAME);
+ onCreate(db);
}
public DBHelper(Context context) {
@@ -103,6 +132,22 @@ public int getCount() {
return count;
}
+ /**
+ * The method returns the count of the files created in table 2 when the paused button is used by the user.
+ * @return
+ */
+
+ public int getCount2() {
+ int count=0;
+ SQLiteDatabase db = getReadableDatabase();
+ String[] projection = { DBHelperItem._ID };
+ Cursor c = db.query(DBHelperItem2.TABLE_NAME, projection, null, null, null, null, null);
+ if(c.getCount()!=0)
+ count = c.getCount();
+ c.close();
+ return count;
+ }
+
public Context getContext() {
return mContext;
}
@@ -115,6 +160,16 @@ public int compare(RecordingItem item1, RecordingItem item2) {
}
}
+ /**
+ * This method stores your audio recoding in the database with following information like - recording name,
+ * file path and length of your audio. After saving your file info in table one. It calls deleteFilesFromTable2() to
+ * delete all the entries from table2.
+ * @param recordingName
+ * @param filePath
+ * @param length
+ * @return
+ */
+
public long addRecording(String recordingName, String filePath, long length) {
SQLiteDatabase db = getWritableDatabase();
@@ -129,9 +184,85 @@ public long addRecording(String recordingName, String filePath, long length) {
mOnDatabaseChangedListener.onNewDatabaseEntryAdded();
}
+ db.close();
+
+ deleteFilesFromTable2();
+
+ return rowId;
+ }
+
+
+ /**
+ * Deletes all the entries from the table2 and also delete all the files created
+ * when recording audio using their absolute paths.
+ */
+ private void deleteFilesFromTable2() {
+ ArrayList myList = getAllAppendingFiles();
+
+ for(int i=0;i getAllAppendingFiles()
+ {
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ ArrayList myList = new ArrayList<>();
+
+ Cursor cursor = db.query(DBHelperItem2.TABLE_NAME,new String[]{DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH
+ },
+ null,null,null,null,null);
+
+ if(cursor.moveToFirst())
+ {
+ do{
+ myList.add(cursor.getString(cursor.getColumnIndex(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH)));
+
+ }while(cursor.moveToNext());
+ }
+
+ db.close();
+
+ return myList;
+
+
+ }
+
public void renameItem(RecordingItem item, String recordingName, String filePath) {
SQLiteDatabase db = getWritableDatabase();
ContentValues cv = new ContentValues();
@@ -159,4 +290,43 @@ public long restoreRecording(RecordingItem item) {
}
return rowId;
}
+
+ /**
+ * Clears table2.
+ */
+
+ public void clearTable2() {
+ SQLiteDatabase db = getWritableDatabase();
+ int countOfRowsdeleted = db.delete(DBHelperItem2.TABLE_NAME, "1", null);
+ Log.d("count",String.valueOf(countOfRowsdeleted));
+ db.close();
+ }
+
+
+ /**
+ * Returns the total length of your audio recorded file by adding the time of all the files in table2.
+ * @return
+ */
+
+ public long getTotalTime() {
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ long time=0;
+
+ /*Cursor cursor = db.query(DBHelperItem2.TABLE_NAME,new String[]{DBHelperItem.COLUMN_NAME_RECORDING_LENGTH
+ },
+ null,null,null,null,null);*/
+
+ Cursor cur = db.rawQuery("SELECT SUM(length) FROM "+DBHelperItem2.TABLE_NAME, null);
+ if(cur.moveToFirst())
+ {
+ time= cur.getLong(0);
+ }
+
+
+ db.close();
+
+ return time;
+
+ }
}
diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java
index a8b36a18..21634c75 100644
--- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java
+++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java
@@ -8,21 +8,36 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.MediaRecorder;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
+import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
+import com.coremedia.iso.boxes.Container;
import com.danielkim.soundrecorder.activities.MainActivity;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
+import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
+import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
import java.io.File;
import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
+import java.util.logging.Handler;
/**
* Created by Daniel on 12/28/2014.
@@ -47,11 +62,28 @@ public class RecordingService extends Service {
private Timer mTimer = null;
private TimerTask mIncrementTimerTask = null;
+
+ /**
+ * Binding this service with the RecordingService fragment for calling the methods of this service from the fragment.
+ */
+
+ IBinder mBinder = new LocalBinder();
+
@Override
public IBinder onBind(Intent intent) {
- return null;
+ return mBinder;
+ }
+
+ /**
+ * This class returns an instance of this service class.
+ */
+ public class LocalBinder extends Binder {
+ public RecordingService getServerInstance() {
+ return RecordingService.this;
+ }
}
+
public interface OnTimerChangedListener {
void onTimerChanged(int seconds);
}
@@ -64,20 +96,87 @@ public void onCreate() {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- startRecording();
+
return START_STICKY;
}
@Override
public void onDestroy() {
- if (mRecorder != null) {
- stopRecording();
+ super.onDestroy();
+ }
+
+
+
+ /**
+ * This methods takes as input all the files which are created when the user used the paused button
+ * and append those files into one file. This method even works when the paused button is not used and only one audio file
+ * is created in table2.
+ *
+ * Refer to this library for better understanding the below method - https://github.com/sannies/mp4parser
+ */
+ public void startAppendingAudio()
+ {
+ try {
+
+
+ String mediaKey = "soun" ;
+ List listMovies = new ArrayList<>();
+
+ // Getting the absolute paths of all the files from table two and saving them in this list;
+ ArrayList myList = mDatabase.getAllAppendingFiles();
+
+ for(int i=0;i listTracks = new LinkedList<>();
+ for (Movie movie : listMovies) {
+ for (Track track : movie.getTracks()) {
+ if (track.getHandler().equals(mediaKey)) {
+ listTracks.add(track);
+ }
+ }
+ }
+ Movie outputMovie = new Movie();
+ if (!listTracks.isEmpty()) {
+ outputMovie.addTrack(new AppendTrack(listTracks.toArray(new Track[listTracks.size()])));
+ }
+ Container container = new DefaultMp4Builder().build(outputMovie);
+
+ long elapsedTime = mDatabase.getTotalTime();
+
+ String fname = getString(R.string.default_file_name)
+ + "_" + (mDatabase.getCount() + 1) + ".mp4";
+
+
+ String fPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+ fPath += "/SoundRecorder/" + fname;
+
+ File f = new File(fPath);
+
+ FileChannel fileChannel = new RandomAccessFile(f, "rw").getChannel();
+ container.writeContainer(fileChannel);
+ fileChannel.close();
+
+ stopRecording(fname,fPath,elapsedTime);
+
+
+ //return true;
+ }
+ catch (IOException e) {
+ Log.e(LOG_TAG, "Error merging media files. exception: "+e.getMessage());
+ //return false;
}
- super.onDestroy();
+
}
+
+
public void startRecording() {
+
setFileNameAndPath();
mRecorder = new MediaRecorder();
@@ -111,8 +210,8 @@ public void setFileNameAndPath(){
do{
count++;
- mFileName = getString(R.string.default_file_name)
- + "_" + (mDatabase.getCount() + count) + ".mp4";
+ mFileName = "paused_recording"
+ + "_" + (mDatabase.getCount2() + count) + ".mp4";
mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + mFileName;
@@ -120,11 +219,57 @@ public void setFileNameAndPath(){
}while (f.exists() && !f.isDirectory());
}
- public void stopRecording() {
- mRecorder.stop();
+
+ /**
+ * After appending audio files from table2 this method stores the newly created audio file in table1.
+ * @param fileName
+ * @param filePath
+ * @param elapsedMilis
+ */
+
+ public void stopRecording(String fileName,String filePath, long elapsedMilis) {
+
+ if(mRecorder!=null)
+ {
+ mRecorder.stop();
+ mRecorder.release();
+ mRecorder = null;
+ }
+
+
+ Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + fileName, Toast.LENGTH_LONG).show();
+
+ //remove notification
+ if (mIncrementTimerTask != null) {
+ mIncrementTimerTask.cancel();
+ mIncrementTimerTask = null;
+ }
+
+
+ try {
+ mDatabase.addRecording(fileName, filePath, elapsedMilis);
+
+ } catch (Exception e){
+ Log.e(LOG_TAG, "exception", e);
+ }
+ }
+
+
+ /**
+ * This method stores the files to table2 when the recording stops (or paused) for the purpose of appending them later.
+ */
+ public void stopRecordingForPause()
+ {
+ if(mRecorder!=null)
+ {
+ mRecorder.stop();
+ mRecorder.release();
+ mRecorder = null;
+ }
+
mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis);
- mRecorder.release();
- Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + mFilePath, Toast.LENGTH_LONG).show();
+
+ //Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + mFilePath, Toast.LENGTH_LONG).show();
//remove notification
if (mIncrementTimerTask != null) {
@@ -132,10 +277,10 @@ public void stopRecording() {
mIncrementTimerTask = null;
}
- mRecorder = null;
+
try {
- mDatabase.addRecording(mFileName, mFilePath, mElapsedMillis);
+ mDatabase.addRecording2(mFileName, mFilePath, mElapsedMillis);
} catch (Exception e){
Log.e(LOG_TAG, "exception", e);
diff --git a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java
index a2e6bde7..58a83fbe 100644
--- a/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java
+++ b/app/src/main/java/com/danielkim/soundrecorder/activities/MainActivity.java
@@ -1,8 +1,11 @@
package com.danielkim.soundrecorder.activities;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
@@ -15,6 +18,7 @@
import com.astuetz.PagerSlidingTabStrip;
import com.danielkim.soundrecorder.R;
+import com.danielkim.soundrecorder.RecordingService;
import com.danielkim.soundrecorder.fragments.FileViewerFragment;
import com.danielkim.soundrecorder.fragments.LicensesFragment;
import com.danielkim.soundrecorder.fragments.RecordFragment;
@@ -27,6 +31,8 @@ public class MainActivity extends ActionBarActivity{
private PagerSlidingTabStrip tabs;
private ViewPager pager;
+
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -37,6 +43,8 @@ protected void onCreate(Bundle savedInstanceState) {
tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs);
tabs.setViewPager(pager);
+
+
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setPopupTheme(R.style.ThemeOverlay_AppCompat_Light);
if (toolbar != null) {
@@ -99,6 +107,8 @@ public CharSequence getPageTitle(int position) {
}
}
+
+
public MainActivity() {
}
}
diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java
index 151822c0..e78a9458 100644
--- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java
+++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java
@@ -1,10 +1,14 @@
package com.danielkim.soundrecorder.fragments;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Environment;
+import android.os.IBinder;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -14,12 +18,15 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.danielkim.soundrecorder.DBHelper;
import com.danielkim.soundrecorder.R;
import com.danielkim.soundrecorder.RecordingService;
import com.melnykov.fab.FloatingActionButton;
import java.io.File;
+import static android.content.Context.BIND_AUTO_CREATE;
+
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
@@ -34,6 +41,8 @@ public class RecordFragment extends Fragment {
private int position;
+ private DBHelper mDatabase;
+
//Recording controls
private FloatingActionButton mRecordButton = null;
private Button mPauseButton = null;
@@ -44,6 +53,9 @@ public class RecordFragment extends Fragment {
private boolean mStartRecording = true;
private boolean mPauseRecording = true;
+ private boolean mBounded;
+ private RecordingService recordingService;
+
private Chronometer mChronometer = null;
long timeWhenPaused = 0; //stores time when user clicks pause button
@@ -69,6 +81,9 @@ public RecordFragment() {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
position = getArguments().getInt(ARG_POSITION);
+ recordingService = new RecordingService();
+
+ mDatabase = new DBHelper(getActivity());
}
@Override
@@ -113,7 +128,7 @@ private void onRecord(boolean start){
if (start) {
// start recording
mRecordButton.setImageResource(R.drawable.ic_media_stop);
- //mPauseButton.setVisibility(View.VISIBLE);
+ mPauseButton.setVisibility(View.VISIBLE);
Toast.makeText(getActivity(),R.string.toast_recording_start,Toast.LENGTH_SHORT).show();
File folder = new File(Environment.getExternalStorageDirectory() + "/SoundRecorder");
if (!folder.exists()) {
@@ -142,6 +157,8 @@ public void onChronometerTick(Chronometer chronometer) {
//start RecordingService
getActivity().startService(intent);
+ recordingService.startRecording();
+
//keep screen on while recording
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -150,15 +167,28 @@ public void onChronometerTick(Chronometer chronometer) {
} else {
//stop recording
- mRecordButton.setImageResource(R.drawable.ic_mic_white_36dp);
- //mPauseButton.setVisibility(View.GONE);
+
+ if(mPauseRecording)
+ {
+ //if paused button is used first save the audio file to table2 and then start appending those audio file
+ // from table2.
+ recordingService.stopRecordingForPause();
+ recordingService.startAppendingAudio();
+ }else
+ {
+ recordingService.startAppendingAudio();
+ mPauseRecording=true;
+
+ }
+ mRecordButton.setImageResource(R.drawable.ic_mic_white_36dp);
+ mPauseButton.setVisibility(View.GONE);
mChronometer.stop();
mChronometer.setBase(SystemClock.elapsedRealtime());
timeWhenPaused = 0;
mRecordingPrompt.setText(getString(R.string.record_prompt));
getActivity().stopService(intent);
- //allow the screen to turn off again once recording is finished
+
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
@@ -166,12 +196,19 @@ public void onChronometerTick(Chronometer chronometer) {
//TODO: implement pause recording
private void onPauseRecord(boolean pause) {
if (pause) {
- //pause recording
+
+ mRecordButton.setImageResource(R.drawable.ic_mic_white_36dp);
+
+ mRecordingPrompt.setText(getString(R.string.record_prompt));
+
+ recordingService.stopRecordingForPause();
+
mPauseButton.setCompoundDrawablesWithIntrinsicBounds
(R.drawable.ic_media_play ,0 ,0 ,0);
- mRecordingPrompt.setText((String)getString(R.string.resume_recording_button).toUpperCase());
+ mRecordingPrompt.setText("Resume or stop recording");
timeWhenPaused = mChronometer.getBase() - SystemClock.elapsedRealtime();
mChronometer.stop();
+
} else {
//resume recording
mPauseButton.setCompoundDrawablesWithIntrinsicBounds
@@ -179,6 +216,43 @@ private void onPauseRecord(boolean pause) {
mRecordingPrompt.setText((String)getString(R.string.pause_recording_button).toUpperCase());
mChronometer.setBase(SystemClock.elapsedRealtime() + timeWhenPaused);
mChronometer.start();
+ recordingService.startRecording();
+
+
}
}
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Intent mIntent = new Intent(getActivity(),RecordingService.class);
+ getActivity().bindService(mIntent, mConnection, BIND_AUTO_CREATE);
+
+ }
+
+ ServiceConnection mConnection = new ServiceConnection() {
+
+ public void onServiceDisconnected(ComponentName name) {
+ mBounded = false;
+ recordingService = null;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mBounded = true;
+ RecordingService.LocalBinder mLocalBinder = (RecordingService.LocalBinder)service;
+ recordingService = mLocalBinder.getServerInstance();
+ }
+ };
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if(mBounded) {
+ getActivity().unbindService(mConnection);
+ mBounded = false;
+ }
+ }
+
+
+
}
\ No newline at end of file