package com.serenegiant.service;
/*
* ScreenRecordingSample
* Sample project to cature and save audio from internal and video from screen as MPEG4 file.
*
* Copyright (c) 2015 saki t_saki@serenegiant.com
*
* File name: ScreenRecorderService.java
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* All files in the folder are under this Apache License, Version 2.0.
*/
import java.io.IOException;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import com.serenegiant.media.MediaAudioEncoder;
import com.serenegiant.media.MediaEncoder;
import com.serenegiant.media.MediaMuxerWrapper;
import com.serenegiant.media.MediaScreenEncoder;
import com.serenegiant.screenrecordingsample.MainActivity;
import com.serenegiant.screenrecordingsample.R;
import com.serenegiant.utils.BuildCheck;
import com.serenegiant.utils.FileUtils;
public class ScreenRecorderService extends Service {
private static final boolean DEBUG = false;
private static final String TAG = "ScreenRecorderService";
private static final String APP_DIR_NAME = "ScreenRecorder";
static {
FileUtils.DIR_NAME = APP_DIR_NAME;
}
private static final String BASE = "com.serenegiant.service.ScreenRecorderService.";
public static final String ACTION_START = BASE + "ACTION_START";
public static final String ACTION_STOP = BASE + "ACTION_STOP";
public static final String ACTION_PAUSE = BASE + "ACTION_PAUSE";
public static final String ACTION_RESUME = BASE + "ACTION_RESUME";
public static final String ACTION_QUERY_STATUS = BASE + "ACTION_QUERY_STATUS";
public static final String ACTION_QUERY_STATUS_RESULT = BASE + "ACTION_QUERY_STATUS_RESULT";
public static final String EXTRA_RESULT_CODE = BASE + "EXTRA_RESULT_CODE";
public static final String EXTRA_QUERY_RESULT_RECORDING = BASE + "EXTRA_QUERY_RESULT_RECORDING";
public static final String EXTRA_QUERY_RESULT_PAUSING = BASE + "EXTRA_QUERY_RESULT_PAUSING";
private static final int NOTIFICATION = R.string.app_name;
private static final Object sSync = new Object();
private static MediaMuxerWrapper sMuxer;
private MediaProjectionManager mMediaProjectionManager;
private NotificationManager mNotificationManager;
public ScreenRecorderService() {
super();
}
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) Log.v(TAG, "onCreate:");
if (BuildCheck.isLollipop())
mMediaProjectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
showNotification(TAG);
}
@Override
public void onDestroy() {
if (DEBUG) Log.v(TAG, "onDestroy:");
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(final Intent intent) {
return null;
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (DEBUG) Log.v(TAG, "onStartCommand:intent=" + intent);
int result = START_STICKY;
final String action = intent != null ? intent.getAction() : null;
if (ACTION_START.equals(action)) {
startScreenRecord(intent);
updateStatus();
} else if (ACTION_STOP.equals(action) || TextUtils.isEmpty(action)) {
stopScreenRecord();
updateStatus();
result = START_NOT_STICKY;
} else if (ACTION_QUERY_STATUS.equals(action)) {
if (!updateStatus()) {
stopSelf();
result = START_NOT_STICKY;
}
} else if (ACTION_PAUSE.equals(action)) {
pauseScreenRecord();
} else if (ACTION_RESUME.equals(action)) {
resumeScreenRecord();
}
return result;
}
private boolean updateStatus() {
final boolean isRecording, isPausing;
synchronized (sSync) {
isRecording = (sMuxer != null);
isPausing = isRecording && sMuxer.isPaused();
}
final Intent result = new Intent();
result.setAction(ACTION_QUERY_STATUS_RESULT);
result.putExtra(EXTRA_QUERY_RESULT_RECORDING, isRecording);
result.putExtra(EXTRA_QUERY_RESULT_PAUSING, isPausing);
if (DEBUG) Log.v(TAG, "sendBroadcast:isRecording=" + isRecording + ",isPausing=" + isPausing);
sendBroadcast(result);
return isRecording;
}
/**
* start screen recording as .mp4 file
* @param intent
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void startScreenRecord(final Intent intent) {
if (DEBUG) Log.v(TAG, "startScreenRecord:sMuxer=" + sMuxer);
synchronized (sSync) {
if (sMuxer == null) {
final int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, 0);
// get MediaProjection
final MediaProjection projection = mMediaProjectionManager.getMediaProjection(resultCode, intent);
if (projection != null) {
final DisplayMetrics metrics = getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
if (width > height) {
// 横長
final float scale_x = width / 1920f;
final float scale_y = height / 1080f;
final float scale = Math.max(scale_x, scale_y);
width = (int)(width / scale);
height = (int)(height / scale);
} else {
// 縦長
final float scale_x = width / 1080f;
final float scale_y = height / 1920f;
final float scale = Math.max(scale_x, scale_y);
width = (int)(width / scale);
height = (int)(height / scale);
}
if (DEBUG) Log.v(TAG, String.format("startRecording:(%d,%d)(%d,%d)", metrics.widthPixels, metrics.heightPixels, width, height));
try {
sMuxer = new MediaMuxerWrapper(this, ".mp4"); // if you record audio only, ".m4a" is also OK.
if (true) {
// for screen capturing
new MediaScreenEncoder(sMuxer, mMediaEncoderListener,
projection, width, height, metrics.densityDpi, 800 * 1024, 15);
}
if (true) {
// for audio capturing
new MediaAudioEncoder(sMuxer, mMediaEncoderListener);
}
sMuxer.prepare();
sMuxer.startRecording();
} catch (final IOException e) {
Log.e(TAG, "startScreenRecord:", e);
}
}
}
}
}
/**
* stop screen recording
*/
private void stopScreenRecord() {
if (DEBUG) Log.v(TAG, "stopScreenRecord:sMuxer=" + sMuxer);
synchronized (sSync) {
if (sMuxer != null) {
sMuxer.stopRecording();
sMuxer = null;
// you should not wait here
}
}
stopForeground(true/*removeNotification*/);
if (mNotificationManager != null) {
mNotificationManager.cancel(NOTIFICATION);
mNotificationManager = null;
}
stopSelf();
}
private void pauseScreenRecord() {
synchronized (sSync) {
if (sMuxer != null) {
sMuxer.pauseRecording();
}
}
}
private void resumeScreenRecord() {
synchronized (sSync) {
if (sMuxer != null) {
sMuxer.resumeRecording();
}
}
}
/**
* callback methods from encoder
*/
private static final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() {
@Override
public void onPrepared(final MediaEncoder encoder) {
if (DEBUG) Log.v(TAG, "onPrepared:encoder=" + encoder);
}
@Override
public void onStopped(final MediaEncoder encoder) {
if (DEBUG) Log.v(TAG, "onStopped:encoder=" + encoder);
}
};
//================================================================================
/**
* helper method to show/change message on notification area
* and set this service as foreground service to keep alive as possible as this can.
* @param text
*/
private void showNotification(final CharSequence text) {
if (DEBUG) Log.v(TAG, "showNotification:" + text);
// Set the info for the views that show in the notification panel.
final Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.app_name)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(createPendingIntent()) // The intent to send when the entry is clicked
.build();
startForeground(NOTIFICATION, notification);
// Send the notification.
mNotificationManager.notify(NOTIFICATION, notification);
}
protected PendingIntent createPendingIntent() {
FileUtils.DIR_NAME = APP_DIR_NAME;
return PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
}
}