Membuat Aplikasi Android Yang Diam-Diam Merekam Video


Pada latihan kali ini, saya akan membuat sebuah aplikasi Android yang akan merekam video secara diam-diam melalui kamera depan tanpa menampilkan preview. Proses perekaman akan tetap berlangsung di balik layar walaupun pengguna membuka aplikasi lain. Tentu saja latihan kali ini tidak banyak gunanya selain untuk membuat ‘kejutan’ bagi teman yang meminjam perangkat Android. Selain itu, karena API Android dirancang untuk menampilkan preview video, maka apa yang saya lakukan pada latihan kali ini bukanlah sesuatu yang resmi didukung dan tidak bekerja dengan baik pada seluruh perangkat Android.

Seperti biasa, saya akan mulai dengan membuat proyek baru di Android Studio. Saya menggunakan perangkat Android versi KitKat. Karena ingin merekam video (termasuk suara) dan menyimpan hasilnya secara publik, saya perlu menambahkan penggunaan permission berikut ini pada AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Agar bisa merekam video, dibutuhkan sebuah SurfaceView untuk keperluan preview. Karena aplikasi berjalan dibalik layar tanpa terikat pada sebuah activity, maka saya akan menambahkan komponen preview ini dalam ukuran 1×1 piksel (sehingga tidak terlihat oleh pengguna) secara langsung pada layar dengan menggunakan WindowManager. Agar bisa melakukan ini, saya membutuhkan permission berikut ini:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Mengapa saya tidak bisa menggunakan activity? Karena sebuah activity akan berhenti pada saat pengguna beralih ke activity lainnya (membuka halaman utama atau aplikasi lain). Agar perekaman bisa tetap berlanjut, saya perlu membuat sebuah service yang tetap berjalan di balik layar. Service ini perlu melakukan proses perekamanan video di dalam thread yang berbeda. Untuk itu, saya membuat sebuah class yang mewakili sebuah thread dengan nama RecorderThread yang isinya seperti berikut ini:

public class RecorderThread extends Thread {

    private boolean aktif;
    private int serviceId;
    private final VideoRecordService recorderService;
    private Camera camera;

    public RecorderThread(int serviceId, VideoRecordService recorderService, Camera camera) {
        this.serviceId = serviceId;
        this.aktif = true;
        this.recorderService = recorderService;
        this.camera = camera;
    }

    @Override
    public void run() {
        // Tentukan lokasi penyimpanan dan nama file
        File picDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "MyRecorder");
        if (!picDir.exists()) {
            picDir.mkdir();
        }
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        final String outputFile = picDir.getAbsolutePath() + File.separator +  "REC_" + timeStamp + ".mp4";
        Log.d("MyRecorder", "Menyimpan ke " + outputFile);

        try {
            // Memulai proses rekaman
            MediaRecorder mediaRecorder = new MediaRecorder();
            camera.unlock();
            mediaRecorder.setCamera(camera);
            mediaRecorder.setOrientationHint(270);
            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
            mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW));
            mediaRecorder.setOutputFile(outputFile);
            mediaRecorder.setPreviewDisplay(recorderService.getDummyPreview().getHolder().getSurface());
            mediaRecorder.prepare();
            mediaRecorder.start();
            aktif = true;
            while (aktif) {
                Thread.sleep(100);
            }
            mediaRecorder.stop();
            mediaRecorder.reset();
            mediaRecorder.release();

            // Publikasikan file
            MediaScannerConnection.scanFile(recorderService, new String[]{outputFile}, new String[]{"video/mp4"}, null);
        } catch (Exception ex) {
            Log.e("MyRecorder", "Terjadi kesalahan saat merekam", ex);
        } finally {
            camera.release();
            recorderService.stopSelf(serviceId);
        }
    }

    public boolean isAktif() {
        return aktif;
    }

    public void setAktif(boolean aktif) {
        this.aktif = aktif;
    }

}

Pada kode program di atas, saya menentukan lokasi penyimpanan file video yang dihasilkan dengan menggunakan Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES). Ini akan menghasilkan lokasi penyimpanan di memori internal, bukan di SD Card! Pada Android 4.4 (KitKat), aplikasi dilarang untuk menulis ke SD Card secara langsung dengan alasan keamanan dan kebersihan setelah uninstall aplikasi.

Untuk memulai perekaman video, saya menggunakan MediaRecorder. Saya harus memanggil method seperti setCamera(), setAudioSource(), setVideoSource(), setProfile(), prepare() dan start() secara berurut sesuai dengan yang dituliskan pada dokumentasi API Android. Saya menggunakan setOrientationHint() untuk mengubah orientasi video karena pada perangkat yang saya pakai, orientasi default untuk kamera depan adalah orientasi mendatar (landscape). Selain itu, pada setAudioSource(), saya menggunakan AudioSource.CAMCORDER untuk menggunakan microphone perekaman video. Perangkat Android modern umumnya dilengkapi dengan 2 microphone: di sisi bawah perangkat untuk percakapan telepon (AudioSource.MIC) dan di sisi atas perangkat untuk percakapan video (AudioSource.CAMCORDER).

Thread ini akan terus merekam video hingga nilai aktif menjadi false. Setelah selesai merekam dan menulis file, saya menggunakan method scanFile() dari MediaScannerConnection untuk memberitahu pada aplikasi lain bahwa ada sebuah file video baru yang dibuat. Dengan demikian, saya dapat langsung memutar file ini pada aplikasi pemutar video bawaan. Bila tidak, file akan muncul di aplikasi pemutar video setelah perangkat di-restart (kecuali pada aplikasi yang langsung mencari file).

Berikutnya, saya perlu membuat sebuah SurfaceView untuk keperluan preview video. Walaupun ingin merekam secara diam-diam, perangkat Android saya akan menolak merekam bila tidak ada preview video. Sebagai contoh, saya membuat file bernama DummyPreview yang isinya seperti berikut ini:

public class DummyPreview extends SurfaceView implements SurfaceHolder.Callback {

    private Camera camera;
    private VideoRecordService videoRecordService;
    private RecorderThread recorderThread;
    private int serviceId;

    public DummyPreview(VideoRecordService videoRecordService, int serviceId) {
        super(videoRecordService);
        this.videoRecordService = videoRecordService;
        this.serviceId = serviceId;
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            camera = Camera.open(1);
            camera.setPreviewDisplay(holder);
            recorderThread = new RecorderThread(serviceId, videoRecordService, camera);
            recorderThread.start();
        } catch (Exception e) {
            Log.e("MyRecorder", "Terjadi kesalahan saat menampilkan preview...", e);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (recorderThread != null) {
            recorderThread.setAktif(false);
        }
    }

    public boolean isAktif() {
        return (recorderThread == null)? false: recorderThread.isAktif();
    }

}

Pada kode program di atas, saya menggunakan Camera.open(1) untuk membuka kamera depan. Setelah itu, saya membuat thread baru (RecorderThread yang sudah saya buat sebelumnya) dan menjalankannya.

Selanjutnya, saya tinggal membuat sebuah service baru. Untuk itu, saya men-klik kanan nama proyek dan memilih menu New, Service, Service. Saya mengisi nama service dengan VideoRecordService dan men-klik tombol Finish untuk selesai. Saya kemudian mengisi kode programnya seperti berikut ini:

public class VideoRecordService extends Service {

    private LocalBinder localBinder = new LocalBinder();
    private DummyPreview dummyPreview;

    public DummyPreview getDummyPreview() {
        return dummyPreview;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        this.dummyPreview = new DummyPreview(this, startId);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(1, 1,
            WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT);
        lp.gravity = Gravity.START | Gravity.TOP;
        wm.addView(dummyPreview, lp);
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        wm.removeViewImmediate(dummyPreview);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }

    public class LocalBinder extends Binder {

        public void matikan() {
            stopSelf();
        }

        public boolean isAktif() {
            return (dummyPreview != null) && dummyPreview.isAktif();
        }

    }

}

Pada kode program di atas, saat service diaktifkan, saya membuat sebuah instance DummyPreview berukuran 1 x 1 dan meletakkannya pada sisi kiri atas layar. Ukurannya yang sangat kecil akan membuatnya tidak terlihat oleh pengguna.

Selanjutnya, untuk mengaktifkan service, saya akan mengubah kode program MainActivity menjadi seperti berikut ini:

public class MainActivity extends ActionBarActivity implements View.OnClickListener {

    private Switch sw;
    private VideoRecordService.LocalBinder binder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout = new LinearLayout(this);
        sw = new Switch(this);
        sw.setTextOn("Perekam Aktif");
        sw.setTextOff("Perekam Tidak Aktif");
        sw.setOnClickListener(this);
        sw.setId(View.generateViewId());
        layout.addView(sw);
        setContentView(layout);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (binder == null) {
            Intent intent = new Intent(this, VideoRecordService.class);
            bindService(intent, serviceConnection, 0);
        }
    }

    @Override
    public void onClick(View v) {
        if (sw.isChecked()) {
            Intent intent = new Intent(this, VideoRecordService.class);
            startService(intent);
            finish();
        } else if (binder != null) {
            binder.matikan();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (binder != null) {
            unbindService(serviceConnection);
            binder = null;
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            binder = (VideoRecordService.LocalBinder) service;
            sw.setChecked(binder.isAktif());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            binder = null;
        }
    };

}

Activity di atas memiliki UI yang hanya terdiri atas sebuah Switch. Bila pengguna mengaktifkannya, maka activity akan ditutup dan proses perekaman akan dimulai. Untuk mematikan proses perekaman, pengguna dapat kembali membuka activity dan mematikan Switch tersebut. Video dapat segera dilihat setelah Switch dimatikan. Pada perangkat yang saya pakai, satu-satunya indikator proses perekaman dimulai dan diselesaikan adalah adanya bunyi shutter. Bila saya tidak menginginkan suara ini, saya bisa men-silent-kan perangkat Android tersebut.

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: