Androidアプリ開発

GoProインタフェースを実装する

この記事は約24分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


HTTP APIでGoProをリモート操作する

ポイント

GoPro(ゴープロ)にはリモート操作するインタフェースとして、HTTP APIOpen 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を習得するのに最適です。まずは無料オンライン相談から♪

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

スポンサーリンク
シェアする
msakiをフォローする
スポンサーリンク

コメント欄

タイトルとURLをコピーしました