Skip to content

Instantly share code, notes, and snippets.

@LouisJenkinsCS
Last active November 15, 2015 01:11
Show Gist options
  • Save LouisJenkinsCS/fac0af7a615c4f365b28 to your computer and use it in GitHub Desktop.
Save LouisJenkinsCS/fac0af7a615c4f365b28 to your computer and use it in GitHub Desktop.
public class RecorderService extends Service {
/**
* Enumerations used to describe the current state of the object, even
* having a direct string representation. For this example, my fragment has a
* text-view which is updated with each state change.
*/
public enum RecorderState {
DEAD("Dead"),
ALIVE("Alive"),
INITIALIZED("Initialized"),
PREPARED("Prepared"),
STARTED("Recording"),
PAUSED("Paused"),
STOPPED("Finished");
private String mState;
RecorderState(String state) {
mState = state;
}
@Override
public String toString() {
return mState;
}
}
/*
Intent category
*/
public static final String RECORDER_STATE_CHANGE = "Recorder State Change";
/*
Key to receive the recorder state over broadcast from intent.
*/
public static final String RECORDER_STATE_KEY = "Recorder State";
/*
Current state of recorder.
*/
private RecorderState mState = RecorderState.DEAD;
private MediaProjection mProjection;
private MediaProjectionManager mManager;
private VirtualDisplay mDisplay;
private MediaRecorder mRecorder;
/**
* Basic initialization block, mostly obtained from a guide which I modified from.
* @param width Width of the display
* @param height Height of the display
* @param audioEnabled Whether or not audio was selected
* @param filename Name of file to create.
*/
private void initialize(int width, int height, boolean audioEnabled, String filename){
mRecorder = new MediaRecorder();
if(audioEnabled) mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setVideoEncodingBitRate(512 * 1000);
mRecorder.setVideoFrameRate(30);
mRecorder.setVideoSize(width, height);
mRecorder.setOutputFile("/sdcard/" + filename);
changeState(RecorderState.INITIALIZED);
}
@Override
public void onCreate() {
super.onCreate();
android.os.Debug.waitForDebugger();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
/*
The intent I send always contains the result code and the extra intent data received from
onActivityResult
*/
if(mProjection == null) {
int resultCode = intent.getIntExtra(ScreenRecorderFragment.RESULT_CODE_KEY, -1);
Intent data = (Intent) intent.getExtras().get(Intent.EXTRA_INTENT);
mManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mProjection = mManager.getMediaProjection(resultCode, data);
mProjection.registerCallback(new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
Toast.makeText(getApplicationContext(), "Stopped recording!", Toast.LENGTH_LONG).show();
}
}, null);
}
if(mState == RecorderState.DEAD) changeState(RecorderState.ALIVE);
return new RecorderBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
/**
* Used to prepare the recorder.
*/
private void prepareRecorder() {
try {
mRecorder.prepare();
changeState(RecorderState.PREPARED);
} catch (IllegalStateException | IOException e) {
logErrorAndChangeState(e);
}
}
/**
* Convenience method to create a virtual display.
* @param width Width of display.
* @param height Heigth of virtual display.
* @return Initialized virtual display.
*/
private VirtualDisplay createVirtualDisplay(int width, int height){
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
return mProjection.createVirtualDisplay(getClass().getName(), width, height,
metrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mRecorder.getSurface(), null, null);
}
public RecorderState getState(){
return mState;
}
/**
* A simple subclass of Binder which allows the user to register onStateChangeListeners, which
* are called whenever the state of the recording changes. I.E, can be used to update a text view
* with the current state.
*/
public class RecorderBinder extends Binder {
/**
* Returns the instance of the service bound to, singleton style.
* @return Instance of RecorderService bound to.
*/
public RecorderService getService() {
return RecorderService.this;
}
}
/**
* Used to log any errors and handle it by changing and broadcasting it's state too, although not sure
* if it would cause a crash or not.
* @param ex Exception.
*/
private void logErrorAndChangeState(Throwable ex){
String msg = ex.getMessage() == null ? "" : ex.getMessage();
Log.wtf(getClass().getName(), "An Error of type: \"" + ex.getClass().getName() + "\" was thrown, during" +
"the recorded state: \"" + mState.toString() + "\", with the message: \"" + msg + "\"!", ex);
changeState(RecorderState.DEAD);
stopSelf();
}
/**
* Change the state and broadcast it to any listeners.
* @param state The state of the recorder.
*/
private void changeState(RecorderState state){
mState = state;
Intent intent = new Intent(RECORDER_STATE_CHANGE);
intent.putExtra(RECORDER_STATE_KEY, mState);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
Log.i(getClass().getName(), "Sent Broadcast Receiver and State is " + mState.toString());
}
/**
* Begin recording. This is called by the Fragment which is bound to this service.
* @param width Width.
* @param height Height.
* @param audioEnabled Is Audio Enabled?
* @param filename Name of file.
*/
public void startRecording(int width, int height, boolean audioEnabled, String filename){
initialize(width, height, audioEnabled, filename);
prepareRecorder();
try {
mDisplay = createVirtualDisplay(width, height);
mRecorder.start();
changeState(RecorderState.STARTED);
} catch (IllegalStateException e){
logErrorAndChangeState(e);
}
}
public void pauseRecording() {
}
@Override
public void onDestroy() {
super.onDestroy();
}
/**
* Cease and desist all recording this instant!
*
* Stops the recorder, resets it, then releases it, which SHOULD be the in the right order
* of it's state. However, the issue is when I call changeState(), for some reason I get an ANR
* and the systemui goes under??? Why.
*/
public void stopRecording() {
try {
mRecorder.stop();
mRecorder.reset();
mDisplay.release();
changeState(RecorderState.STOPPED);
} catch(IllegalStateException e){
logErrorAndChangeState(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment