この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)
HTTP APIでGoProをリモート操作する
ポイント
GoPro(ゴープロ)にはリモート操作するインタフェースとして、HTTP APIとOpen GoProがあります。
Open GoProはHERO9以降で使用できるBLE、Wi-Fi、USBでハンドリングできるコマンドセットです。
HTTP APIはHERO8以前から使用できるHTTPベースのコマンドセットです。
今回は、GoPro全般で使用できるHTTP APIでGoProをリモート操作するGoProインタフェースを紹介します。
HTTPのハンドリングではOkHttpを使用します。
Open GoPro
The unofficial API for GoPro cameras (The WiFi enabled models)
OkHttp
コマンドを実行するにあたり、デバイスとネットワーク接続する必要があります。
Wi-Fiデバイスにネットワーク接続はこちら↓↓↓
GoproHelperクラス
HTTP APIのコマンド実行はGoproHelperクラスとして実装します。
JSONデータでコマンドを生成、OkHttpでコマンドを実行します。
build.gradle(Module :app)
dependencies {
implementation "com.squareup.okhttp3:okhttp:5.3.2"
:
}コマンドはデバイスのIPアドレス宛てにPOSTリクエストを行います。
ステータス取得、録画開始、メディア取得、コマンド実行、ダウンロード、削除のメソッドを用意します。
リクエストの応答を待つ必要があるため、非同期処理として実装する必要があります。
GoproHelper
public class GoproHelper{
public static final String START_CAPTURE = "camera.startCapture";
public static final String STOP_CAPTURE = "camera.stopCapture";
private final OkHttpClient client = new OkHttpClient();
private final Handler handler = new Handler(Looper.getMainLooper());
private final ObjectMapper mapper = new ObjectMapper();
private final Context context;
public String[] response;
public State[] state;
public List<String> deletes = new ArrayList<>();
public String fileUrls;
private final String STAT;
private final String COMM;
private final String LIST;
private final String DOWN;
private int retry = 0;
public Media[] media;
public GoproHelper(Context context, int slot, String ip) {
this.context = context;
this.response = new String[slot];
this.state = new State[slot];
this.media = new Media[slot];
this.STAT = "http://" + ip + "/gp/gpControl/status";
this.COMM = "http://" + ip + "/gp/gpControl/command/%s";
this.LIST = "http://" + ip + "/gp/gpMediaList";
this.DOWN = "http://" + ip + "/videos/DCIM%s";
}
public void state(int slot) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
MediaType type = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create("", type);
// status
Request request = new Request.Builder()
.url(STAT)
.post(body)
.build();
try (Response res = client.newCall(request).execute()) {
if (!res.isSuccessful()) {
throw new IOException("Unexpected code " + res);
}
response[slot] = res.body().string();
} catch (IOException e) {
// エラー処理を記述する
:
}
handler.post(() -> {
// ここに処理を記述する
:
});
});
}
public void recording(int slot) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
MediaType type = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create("", type);
// Primary modes:VIDEO
Request request = new Request.Builder()
.url(String.format(COMM, "mode?p=0"))
.post(body)
.build();
try (Response res = client.newCall(request).execute()) {
if (!res.isSuccessful()) {
throw new IOException("Unexpected code " + res);
}
response[slot] = res.body().string();
} catch (IOException e) {
// エラー処理を記述する
:
}
handler.post(() -> {
fileUrls = null;
execute(START_CAPTURE, slot, false);
});
});
}
private void media(String command, int slot) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
MediaType type = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create("", type);
// Media List
Request request = new Request.Builder()
.url(LIST)
.post(body)
.build();
try (Response res = client.newCall(request).execute()) {
if (!res.isSuccessful()) {
throw new IOException("Unexpected code " + res);
}
response[slot] = res.body().string();
media[slot] = mapper.readValue(response[slot], Media.class);
} catch (IOException e) {
response[slot] = null;
}
Runnable runnable = () -> media(command, slot);
if (media[slot] != null) {
handler.post(() -> {
fileUrls = String.format(DOWN, media[slot].file());
// ここに処理を記述する
:
});
} else {
if (retry < 10) {
retry++;
handler.postDelayed(runnable, 1000);
} else {
// エラー処理を記述する
:
}
}
});
}
public void execute(String command, int slot) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
MediaType type = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create("", type);
// Shutter
Request request = new Request.Builder()
.url(String.format(COMM, command.equals(START_CAPTURE)? "shutter?p=1" : "shutter?p=0"))
.post(body)
.build();
try (Response res = client.newCall(request).execute()) {
if (!res.isSuccessful()) {
throw new IOException("Unexpected code " + res);
}
response[slot] = res.body().string();
} catch (IOException e) {
// エラー処理を記述する
:
}
Runnable runnable = () -> media(command, slot);
if (command.equals(STOP_CAPTURE)) {
retry = 0;
handler.postDelayed(runnable, 2000);
} else {
handler.post(() -> {
// ここに処理を記述する
:
}
}
});
}
public void download(String url, Uri uri, int slot) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
ResponseBody body = response.body();
try (InputStream inputStream = body.byteStream();
OutputStream outputStream = new FileOutputStream(Objects.requireNonNull(context.getContentResolver().openFileDescriptor(uri, "wt")).getFileDescriptor())) {
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
byte[] buffer = new byte[1024];
int len;
while ((len = Objects.requireNonNull(inputStream).read(buffer)) != -1) {
bufferedOutputStream.write(buffer, 0, len);
}
bufferedOutputStream.close();
} catch (IOException e) {
// エラー処理を記述する
:
}
} else {
// エラー処理を記述する
:
}
} catch (IOException e) {
// エラー処理を記述する
:
}
handler.post(() -> {
// ここに処理を記述する
:
});
});
}
public void delete(int slot) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
MediaType type = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create("", type);
for (String delete : deletes) {
// delete
Request request = new Request.Builder()
.url(String.format(COMM, String.format("storage/delete?p=%s", delete)))
.post(body)
.build();
try (Response res = client.newCall(request).execute()) {
if (!res.isSuccessful()) {
throw new IOException("Unexpected code " + res);
}
response[slot] = res.body().string();
} catch (IOException e) {
// エラー処理を記述する
:
}
}
handler.post(() -> {
// ここに処理を記述する
:
});
});
}
}録画した動画ファイルを取得はメディア情報を取得し、作成日時が最新のファイルを取得します。
ポイントとしては、録画停止から少し時間がかかります。
録画停止から2秒後にメディア情報を取得し、取得できない場合は1秒後に再取得します。
メディア情報を取得できない場合、HTTPレスポンスコードは503です。
メディア情報はJSON形式でディレクトリ階層毎にファイル情報が配列です。
Mediaクラスを用意し、ObjectMapperでMediaクラスに展開します。
Media
public class Media {
public String id;
public List<media> media;
public static class media {
public String d;
public List<fs> fs;
}
public static class fs {
public String n;
public String cre;
public String mod;
public String glrv;
public String ls;
public String s;
}
@NonNull
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (media media1 : media) {
for (fs fs1 : media1.fs) {
stringBuilder.append(String.format("Media [id=%s, d=%s, n=%s, cre=%s, mod=%s, glrv=%s, ls=%s, s=%s]\n", id, media1.d, fs1.n, fs1.cre, fs1.mod, fs1.glrv, fs1.ls, fs1.s));
}
}
return stringBuilder.toString();
}
public int count() {
int count = 0;
for (com.jiseifirm.acamrs.entity.Media.media media1 : media) {
count += media1.fs.size();
}
return count;
}
public String file() {
String d = "";
fs file = new fs();
file.cre = "0";
for (media media1 : media) {
for (fs fs1 : media1.fs) {
if (Long.parseLong(fs1.cre) > Long.parseLong(file.cre)) {
d = media1.d;
file = fs1;
}
}
}
return d.isEmpty() || file.n.isEmpty()? null : String.format("/%s/%s", d, file.n);
}
}ファイル数のカウント(count)、最新ファイルを取得(file)するメソッドを用意します。
使用方法
レスポンスを受ける配列数、接続デバイスのIPアドレスでGoproHelperクラスのインスタンス化します。
録画停止後にメディア情報を取得、最新のファイル情報をダウンロードまたは、削除で使用します。
接続デバイスにもよりますが、Wi-Fi接続で通信がない場合にWi-Fi自動オフ機能が働くことがあります。
Wi-Fi自動オフ機能を無効化するために一定間隔でキープアライブ通信を実行します。
キープアライブはステータス取得を使用します。
private GoproHelper goproHelper;
:
@Override
public void onCreate() {
super.onCreate();
:
goproHelper = new GoproHelper(context, 5, IP);
:
}
// 録画開始
goproHelper.recording(1);
:
// 録画停止
goproHelper.execute(goproHelper.STOP_CAPTURE, 2);
:
// ダウンロード
Uri uri = "ダウンロードファイルのUri";
goproHelper.download(goproHelper.fileUrls, uri, 5);
:
// ファイル削除
// deletesに削除対象のUriをセットする
goproHelper.delete(4);
:
// キープアライブ
goproHelper.state(0);
:
今回は、ここまでです。
GoProインタフェースでGoProをリモート操作しているAndroidアプリです。
誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にあるコメントか、
こちらから、お願いいたします♪
ポチッとして頂けると、
次のコンテンツを作成する励みになります♪
これからAndroidのアプリ開発やJavaでの開発を始めたい方へ
アプリケーション開発経験がない方や、アプリケーション開発経験がある方でも、JavaやC#などのオブジェクト指向言語が初めての方は、Androidのアプリ開発ができるようになるには、かなりの時間がかかります。
オンラインスクールでの習得を、強くおススメします。
未経験者からプログラマーを目指すのに最適です。まずは無料カウンセリングから♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適です。まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪





コメント欄