Last active
November 15, 2015 01:11
-
-
Save LouisJenkinsCS/fac0af7a615c4f365b28 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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