I love automating things, especially the boring ones. Having a Youtube channel, I found that editing videos can be very time-consuming and so I started thinking about ways to improve my workflow.

My main bottlenecks are cutting the video clips and transferring the videos to the PC. Because I record those videos with my smartphone, I wanted an app that could:

  • Control the camera from my PC
  • Organize the clip files in a way that makes editing easier
  • Send the clips automatically to my computer

I searched for a while on the Play Store, but I couldn’t find an app that completely satisfied my needs and so, being a developer, I decided to create my own.

Forking Open Camera

Creating a Camera App isn’t a trivial task, so it made sense to start from an existing project. Luckily for me, Mark Harman created Open Camera, an awesome open-source camera App for Android that I used as a starting point.

The goal at this point was clear, forking Open Camera and adding the remote control and file transfer functionality.

The Open Camera repository is hosted on Sourceforge but I wanted to fork it on Github. Thanks to the Github Importer utility, the process was painless.

Forking Open Camera on Github

Compile the Sources

After cloning the repository, I was ready to open the project with Android Studio to study the source, but first, I had to check if I could compile the app.

I started the compilation process, but as I already had Open Camera installed on my smartphone, I got a naming conflict:

Naming Conflict Package Android

This happened because both the original and my forked version shared the same package id. To fix this problem, all I had to do was to rename the package. All occurrences of net.sourceforge.opencamera had been replaced with com.federicoterzi.opencamerastudio.

This was easily accomplished with the refactoring functions of Android Studio, combined with the very useful Find in Path action.

It’s also necessary to change the folder structure to reflect the new package name. This StackOverflow question helped me in the process.

At this point, I had a shiny new app on my smartphone, Open Camera Studio, ready to be tweaked.

Studying the Source

I wanted to manage the camera app remotely, so the next thing to do was to find out how Open Camera controlled the recordings.

I started digging in the source code. I wanted to find the UI code that triggered the recording and the class MainUI caught my attention.

Open Camera MainUI class

Scrolling through the source, I stumbled upon the onKeyDown method, apparently used to manage the events from volume buttons and selfie sticks, which in turn called the MainActivity’s takePicture method.

Open Camera Take Picture

After a bit of experimenting, I determined that the takePicture method enabled both the start and the stop of a video recording, so I was ready for the next phase.

Adding Remote Superpowers

There are many ways to make the app capable of receiving remote commands, but I found that the most versatile one is the bundled web server approach: a basic HTTP web server is started in the app itself and other devices in the network can send requests ( such as commands ) and get responses ( such as video files ).

This solution also has the additional advantage that it could be possible to create a Web App, hosted by the web server itself, to control the smartphone from any web browser in the network. I chose a different route though because I wanted to keep things simple.

The easiest way to bundle a web server on Android is the NanoHTTPD Library, which can be used by including the following dependency in the Gradle config file:

implementation 'org.nanohttpd:nanohttpd:2.3.1'

I was also necessary to add the following permissions in the AndroidManifest.xml:

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

I then created the StudioServer class extending the NanoHTTPD class and overriding the serve method. This function will be called anytime a client makes an HTTP request to the bundled webserver.

At this point, I needed a way to trigger the takePicture method when the serve method was called. Unfortunately, I could not call it directly as the takePicture method must be called from the main thread. So I used the powerful LocalBroadcastManager class to setup a receiver in the MainActivity that called the takePicture method and a broadcast from the serve method to trigger it:

BroadcastReceiver

In the MainActivity I added the receiver code:

private BroadcastReceiver studioCommandReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            takePicture(false);
        }
    };

And the code to register/unregister it:

public static final String STUDIO_BROADCAST_ID = "STUDIO_BROADCAST";

// Inside the onResume() method
LocalBroadcastManager.getInstance(this).registerReceiver(studioCommandReceiver,
                new IntentFilter(STUDIO_BROADCAST_ID));

// Inside the onPause() method
LocalBroadcastManager.getInstance(this).unregisterReceiver(studioCommandReceiver);

Server

StudioServer class:

package com.federicoterzi.opencamerastudio.studio
import android.content.Intent
import android.support.v4.content.LocalBroadcastManager
import com.federicoterzi.opencamerastudio.MainActivity
import fi.iki.elonen.NanoHTTPD
import java.io.File
import java.io.IOException

class StudioServer(val mainActivity: MainActivity, val port : Int) : NanoHTTPD(port) {

    override fun serve(session: IHTTPSession): Response {
        val intent = Intent(MainActivity.STUDIO_BROADCAST_ID)
        LocalBroadcastManager.getInstance(mainActivity).sendBroadcast(intent)
        return newFixedLengthResponse("OK")
    }
}

And finally, the code to start the StudioServer in the MainActivity:

private StudioServer studioServer;

// In the onCreate() method
studioServer = new StudioServer(this, 8000);

// In the onResume() method
try {
    studioServer.start();
} catch (IOException e) {
    e.printStackTrace();
}

// In the onPause() method
studioServer.stop();

Now every time the MainActivity is opened, a WebServer is started on port 8000.

Let’s say the smartphone has 192.168.1.100 as IP, then any other device in the same network can access the server at the address: http://192.168.1.100:8000/. Anytime a request is made, the app should start/stop a recording.

What’s next?

This was a very basic proof of concept you can use as a starting point when building something similar. I also added many features to the app, such as file transfer and clip naming, that you can check out in the Github Repository.

In the next article, we will see how to create the Desktop control panel in Go.

Open Camera Studio Remote Desktop App

Stay tuned :)