MicrocosmWorksInovasi dan Seni Bina Kosmos Digital
TentangHubungi
MicrocosmWorksMemperbaharui dan Merangka Kosmos Digital

Menyampaikan penyelesaian IT yang penting. Kami bersemangat tentang teknologi, keselamatan, dan membantu perniagaan berkembang melalui infrastruktur IT yang boleh dipercayai dan inovatif.

[email protected]
+91 7011868196
New Delhi, India

Pusat Pertumbuhan AI

AI HubInovasi PermulaanPemecut Perusahaan

Penyelesaian

Semua PenyelesaianAplikasi Kesihatan & KecergasanPlatform Video AIPembangunan Ejen AI

Sumber

WawasanPanduan IndustriPelan Tindakan Kes PenggunaanCorak Seni BinaKajian Kes

Syarikat

Tentang KamiHubungiKerja Kami

Perkhidmatan

Perundingan DigitalInfrastruktur AwanPembangunan SaaSPembangunan AITeknologi Video
Pembangunan ERPPenyesuaian ZohoPembangunan OdooIntegrasi SalesforcePembangunan CRM Tersuai
Integrasi QuickBooksPenyelesaian IoTPembangunan Blockchain
Perundingan Keselamatan SiberSokongan IT - L3

© 2026 MicrocosmWorks. Hak cipta terpelihara.

Dasar PrivasiTerma Perkhidmatan
Kembali ke Wawasan
AI Development

Mengawal GoPro Melalui WiFi dalam Flutter: API HTTP Open GoPro, Keep-Alives, dan Pemindahan Fail Penstriman

Menyambung ke GoPro melalui WiFi dari Flutter menggunakan API HTTP Open GoPro, dengan Keep-Alives dan pemindahan fail distrim.

Saurav Kumar Gupta's image Saurav Kumar Gupta
•
July 17, 2026
•
Dikemas kini July 30, 2026
•
2 min read
GoPro camera connected to a Flutter app over Wi-Fi for remote control and media file transfer..webp
2 min read

Tiada SDK, tiada tarian pasangan BLE — hanya IP tetap kamera, klien Dio, dan beberapa "gotchas" yang akan merosakkan hari anda secara senyap jika anda tidak tahu mengenainya.

Mengapa WiFi, bukan Bluetooth?

Kebanyakan tutorial "sambung ke GoPro" bermula dengan Bluetooth Low Energy: imbas, pasangkan, tukar ciri-ciri GATT, kemudian gunakan BLE untuk menghidupkan WiFi, kemudian pindahkan melalui WiFi juga. Ia berfungsi, tetapi ia melibatkan banyak upacara — dan BLE perlahan serta rumit untuk perkara yang pengguna inginkan, iaitu mengawal kamera dan memindahkan rakaman daripadanya dengan cepat.

Jadi, dalam aplikasi ini kami melangkau BLE sepenuhnya. Pengguna menyambung ke titik akses WiFi GoPro dari tetapan telefon mereka, dan dari titik itu dan seterusnya, semuanya adalah HTTP biasa terhadap API Open GoPro kamera pada alamat tetap:

http://10.5.5.9:8080

Kawalan kamera, tetapan, penyenaraian media, dan muat turun fail semuanya adalah permintaan HTTP GET ke IP tersebut. Keseluruhan integrasi adalah 100% Dart dengan pakej Dio — sifar Kotlin/Swift asli. Itulah tajuk utama, dan seperti yang akan kita lihat di akhir, ia juga merupakan satu-satunya tempat di mana reka bentuk ini rapuh.

Aliran:

sertai AP WiFi GoPro (tetapan telefon)
   → kesan kamera boleh dicapai (probe TCP ke 10.5.5.9:8080)
   → sambung + kenal pasti model (GET /gopro/camera/state)
   → mulakan pemasa Keep-Alive (30s) + poll status (5s)
   → kawalan: shutter / mode / settings
   → senaraikan media (GET /gopro/media/list)
   → muat turun dengan turbo + kemajuan (dio.download)

1. Mengesan kamera tanpa kebenaran imbasan WiFi

Naluri pertama adalah untuk menyenaraikan SSID WiFi dan mencari yang bermula dengan GoPro. Jangan — itu akan menarik kebenaran lokasi dan API imbasan WiFi khusus platform.

GoPro sentiasa berada pada IP yang sama, jadi pengesanannya hanyalah: bolehkah saya membuka soket TCP ke 10.5.5.9:8080? Tiada HTTP, tiada kebenaran, tiada penghuraian SSID.

static const String goProIp = '10.5.5.9';
static const int goProPort = 8080;
static const Duration checkInterval = Duration(seconds: 2);
static const Duration connectionTimeout = Duration(seconds: 3);

Future<void> _checkConnection() async {
  try {
    final socket = await Socket.connect(goProIp, goProPort, timeout: connectionTimeout);
    socket.destroy();
    if (_currentState != GoProWifiState.connected) {
      _currentState = GoProWifiState
          .connected;
      _connectionStateController.add(_currentState);
    }
  } catch (e) {
    if (_currentState != GoProWifiState
          .disconnected) {
      _currentState = GoProWifiState
          .disconnected;
      _connectionStateController.add(_currentState);
    }
  }
}

Kami mengundi itu setiap 2 saat dan menyiarkan perubahan keadaan melalui aliran. Skrin sambungan melanggan dan bertukar kepada "connected" saat soket dibuka. Kerana tiada penemuan peranti sebenar, perkhidmatan ini mensintesis CameraDevice maya (berkunci dari IP tetap) supaya abstraksi kamera yang lain tidak perlu tahu perbezaannya.

2. Klien HTTP: Dio dan titik akhir Open GoPro

Klien adalah pembalut Dio yang nipis. Perhatikan had masa sambungan yang singkat (anda berada di LAN — jika ia perlahan, ia rosak) dan had masa penerimaan yang murah hati untuk panggilan kawalan:

static const String baseUrl = 'http://10.5.5.9:8080';

GoProHttpClient() {
  _dio = Dio(BaseOptions(
    baseUrl: baseUrl,
    connectTimeout: const Duration(seconds: 5),
    receiveTimeout: const Duration(seconds: 10),
    headers: {'Accept': 'application/json'},
  ));
}

API Open GoPro adalah seragam dengan menyenangkan: semuanya adalah HTTP GET, termasuk arahan dan perubahan tetapan. Berikut adalah titik akhir yang sebenarnya digunakan oleh aplikasi ini:

TujuanKaedah + LaluanPertanyaan
Start shutter (record / photo)GET /gopro/camera/shutter/start—
Stop shutterGET /gopro/camera/shutter/stop—
Keep-aliveGET /gopro/camera/keep_alive—
Full state (status + settings)GET /gopro/camera/state—
Set preset groupGET /gopro/camera/presets/set_groupid (1000/1001/1002)
Change a settingGET /gopro/camera/settingsetting, option
Media listGET /gopro/media/list—
Download a fileGET /videos/DCIM/{dir}/{file}(bytes)
Thumbnail / screennailGET /gopro/media/thumbnail / .../screennailpath
Turbo transferGET /gopro/media/turbo_transferp (1/0)
Delete .360 group (legacy)GET /gp/gpControl/command/storage/delete/groupp

Perincian yang menarik: kedua-dua keluarga /gopro/... moden dan keluarga /gp/gpControl/... legasi wujud bersama. Pembuangan fail kumpulan chaptered.360/GS, yang tidak dapat dikendalikan dengan bersih oleh API baharu, adalah satu-satunya situasi di mana kami menggunakan titik akhir lama.

Corak pemulihan diri 403 (helah paling bersih di sini)

Anda tidak dapat mengetahui terlebih dahulu tetapan mana yang sah dalam mod kamera semasa — resolusi yang sah dalam 16:9 adalah tidak sah dalam 9:16, pilihan FPS bergantung pada resolusi, dan sebagainya. GoPro memberitahu anda ini dengan cara yang sukar: ia mengembalikan HTTP 403 dengan senarai pilihan yang seharusnya sah, dan HTTP 500 apabila ia hanya sibuk seketika.

Jadi kami menghuraikan badan 403 menjadi ralat bertaip dan cuba semula 500:

Future<void> setSetting(
  int settingId, int optionValue,) async {
  const maxRetries = 3;
  for (int attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await _dio.get('/gopro/camera/setting',
          queryParameters: {'setting': settingId, 'option': optionValue});
      return;
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.badResponse) {
        final statusCode = e.response?.statusCode;
        if (statusCode == 403) {
          final body = e.response?.data;
          List<int> availableIds = [];
          if (body is Map<String, dynamic>) {
            final options = body['available_options'];
            if (options is List) {
              availableIds = options
                  .whereType<Map<String, dynamic>>()
                  .map((o) => o['id'] as int? ?? -1)
                  .where((id) => id >= 0).toList();
            }
          }
          throw SettingRejectedError(
            settingId: settingId, rejectedOption: optionValue,
            availableOptionIds: availableIds);
        }
        if (statusCode == 500 && attempt < maxRetries) {
          await Future.delayed(Duration(milliseconds: attempt * 500)); // linear backoff
          continue;
        }
      }
      rethrow;
    }
  }
}

Pilihan yang dilaporkan oleh kamera digunakan oleh lapisan perkhidmatan untuk menulis semula senarai keupayaannya sendiri jika ia mengesan SettingRejectedError. Untuk setiap model, kami bermula dengan jadual keupayaan yang optimistik dan berkod keras dan membenarkan kamera membetulkan kami sepanjang waktu pelaksanaan. Ia lebih praktikal dan membetulkan diri daripada cuba mensimulasikan keseluruhan matriks tetapan GoPro di awal.

3. Keep-alive: pemasa yang tidak boleh anda lupakan

Selepas kira-kira 60 saat tidak aktif, AP WiFi GoPro menamatkan sesi anda. Sambungan anda yang sempurna akan terputus secara senyap di tengah-tengah sesi jika anda tidak "mencuitnya". Oleh itu, kami memulakan dua pemasa semasa sambungan:

  • Keep-alive setiap 30 s → GET /gopro/camera/keep_alive
  • Poll status setiap 5 s → GET /gopro/camera/state

void _startKeepAlive() {
  _keepAliveTimer?.cancel();
  _keepAliveTimer = Timer.periodic(
    GoProHttpConstants.keepAliveInterval, // 30s
    (_) => _sendKeepAlive(),
  );
}

Future<void> _sendKeepAlive() async {
  if (!isConnected || _isDisposed) return;
  try {
    await _httpClient.keepAlive();
  } catch (e) {
    if (!_isDisposed) _handleDisconnection(); // a failed keep-alive == we're disconnected
  }
}

Keep-alive yang gagal juga merupakan isyarat pemutusan sambungan kami — ia adalah cara paling boleh dipercayai untuk menyedari pengguna telah keluar dari julat WiFi.

4. Kawalan kamera: shutter, mode, dan tarian lensa MAX 2

Shutter adalah bahagian yang mudah — mulakan, tunggu 500 ms, undi semula status. Penangkapan foto adalah shutter/start yang sama; sama ada anda mendapat video atau foto bergantung pada mod semasa.

Penukaran mod menjadi menarik, kerana "mod" pada GoPro MAX 2 sebenarnya adalah dua tetapan: kumpulan pratetap (Video = 1000, Foto = 1001, Timelapse = 1002) ditambah lensa (Tetapan 194: 0 = lensa tunggal/HERO, 1 = 360). Memperbetulkan ini memerlukan beberapa peraturan yang tidak jelas yang diselitkan dalam kod:

  1. Selaraskan perubahan mod. Ketukan sub-mod pantas diaturkan melalui rantai Completer supaya tidak saling berlumba ke keadaan yang tidak konsisten.
  2. Hentikan shutter terlebih dahulu. Firmware menolak perubahan pratetap dengan HTTP 400 semasa merakam, jadi kami secara proaktif menghentikan pengekodan sebelum bertukar.
  3. Susunan dan kelewatan adalah penting. Pada MAX 2: tetapkan kumpulan pratetap dahulu (+400 ms), kemudian lensa melalui Tetapan 194 (+1500 ms). Jeda 1.5 saat itu adalah nyata — ia adalah peralihan pemasangan lensa fizikal.
  4. Percayai keadaan yang diinginkan, bukan keadaan yang di-poll. Kami menjejaki _lastIntendedIs360 / _lastIntendedPresetGroup dan membandingkan dengannya, kerana poll status 5 saat boleh memberikan anda gambaran lama di pertengahan peralihan.

Terdapat juga ciri GoPro yang sangat licik yang patut disebut: ID resolusi bergantung pada nisbah aspek. 4K adalah pilihan 1 dalam 16:9, 109 dalam 9:16, dan 112 dalam 4:3. Jadi selepas menukar nisbah aspek, kami membuat permintaan semula keupayaan — ID yang sama akan bermaksud resolusi yang berbeza.

5. Menyenaraikan media

GET /gopro/media/list mengembalikan struktur yang dikelompokkan mengikut direktori dengan kunci yang ringkas: d = direktori, fs = fail, n = nama fail, s = saiz, cre/mod = cap waktu unix-saat, g = ID kumpulan, glrv = saiz proksi-resolusi rendah.

final media = response['media'] as List<dynamic>?;
for (final dir in media) {
  final directory = dirMap['d'] as String? ?? '';
  final filesList = dirMap['fs'] as List<dynamic>?;
for (final fileJson in filesList) {
    files.add(GoProCameraFile.fromJson(directory, fileMap));
  }
}
files.sort((a, b) => b.modifiedAt.compareTo(a.modifiedAt)); // terbaru dahulu

Jenis media disimpulkan daripada awalan + sambungan nama fail: GS = video 360, GX = video HERO, GT = timelapse, .360 = video sfera, .GPR/.RAW = foto mentah. Awalan-awalan ini penting kemudian, kerana fail .360 berkelakuan berbeza daripada yang lain.

6. Muat turun penstriman: turbo, kemajuan, dan fail iringan

Pemindahan bait sebenar adalah dio.download() terus ke laluan fail, dengan had masa penerimaan yang panjang kerana klip .360 bersaiz 1 GB mengambil masa.

Future<void> downloadMediaToFile(
  String directory, String filename, String destPath,
  {void Function(int received, int total)? onProgress}) async {
  await _dio.download(
    '/videos/DCIM/$directory/$filename',
    destPath,
    onReceiveProgress: onProgress,
    options: Options(receiveTimeout: const Duration(minutes: 30)),
  );
}

Di sekeliling inti itu, perkhidmatan pemindahan mengatur butiran dunia nyata:

Pemindahan Turbo. Sebelum memuat turun, kami menghidupkan mod turbo (turbo_transfer?p=1) — GoPro bertukar kepada WiFi 5 GHz untuk pemindahan yang lebih pantas — dan kami mematikannya dalam blok finally supaya ia sentiasa dibersihkan, walaupun berlaku ralat.

Kemajuan + kelajuan, disekat. Mengira kelajuan pemindahan pada setiap panggilan balik onReceiveProgress adalah membazir dan tidak stabil, jadi kami mengira semula hanya setiap 500 ms:

onProgress: (received, total) {
  if (_isCancelled) return;
  final now = DateTime.now();
  String? speed;
  if (now.difference(lastSpeedUpdate).inMilliseconds >= 500) {
    final bytesPerSecond = (received - lastBytes) /
        (now.difference(lastSpeedUpdate).inMilliseconds / 1000);
    speed = _formatSpeed(bytesPerSecond.round());
    lastSpeedUpdate = now; lastBytes = received;
  }
  _transferController.add(GoProFileTransfer(
    status: FileTransferStatus.downloading,
    progress: total > 0 ? received / total : 0,
    speed: speed,
    currentBytes: received,
    totalBytes: total));
}

Pembatalan adalah bendera _isCancelled koperatif; apabila dibatalkan, kami memadamkan fail separa dan mengeluarkan acara dibatalkan.

Fail iringan. Ini adalah bahagian yang tidak akan anda temui dalam dokumentasi. Untuk .MP4 lensa tunggal, kami juga menarik .LRV (proksi resolusi rendah yang dirakam GoPro bersama video penuh) dan lakaran kecil .THM, kemudian mengindeks LRV untuk main semula dalam aplikasi yang pantas. Dua "gotchas" penamaan yang menyusahkan di sini:

  • HERO8+ menamakan semula proksi: GX######.MP4 / GH######.MP4 menjadi GL######.LRV (aksara kedua bertukar kepada L), dengan fallback nama yang sama untuk kamera yang lebih lama.
  • Fail .360 tidak menyokong titik akhir lakaran kecil atau screennail sama sekali. Kami kembali menggunakan fail iringan .THM, dan sebagai pilihan terakhir memuat turun fail itu sendiri. Dan kerana .360 adalah dual-fisheye, kami memotong lakaran kecil ke lensa hadapan — pada isolat latar belakang melalui compute(), supaya UI tidak pernah tergendala.

Status pemindahan didedahkan melalui Riverpod notifier yang melanggan aliran fail dan pemindahan, memastikan pemindahan yang selesai kelihatan selama 2 saat, dan memaparkan ralat selama 5. Perlu diingatkan: kawalan dan pemindahan fail menggunakan dua klien Dio yang berasingan ke kamera yang sama, jadi muat turun boleh berjalan sementara poll status dan Keep-Alive diteruskan.

7. Dua "gotchas" yang tiada dalam mana-mana tutorial

HTTP Cleartext disekat secara lalai pada Android moden

http://10.5.5.9:8080 adalah teks biasa, dan Android 9+ menyekat trafik cleartext secara lalai. Anda perlu membenarkannya secara eksplisit:

<!-- AndroidManifest.xml -->
<application android:usesCleartextTraffic="true" ... >

ditambah network_security_config.xml yang konfigurasi asasnya membenarkan cleartext (anda masih boleh memaksa HTTPS untuk domain awan/backend anda sendiri). Terlepas ini dan setiap panggilan GoPro akan gagal dengan ralat sambungan yang mengelirukan.

Ikatan rangkaian yang hilang — titik rapuh reka bentuk

Ini adalah bahagian yang jujur. Pada Android moden, apabila anda menyertai AP GoPro, telefon biasanya mengekalkan selular sebagai rangkaian lalai kerana AP GoPro tiada internet. Itu bermakna permintaan HTTP ke 10.5.5.9 boleh dihalakan keluar melalui selular dan gagal — walaupun anda "connected" ke kamera.

Penyelesaian yang teguh adalah asli: panggil ConnectivityManager.bindProcessToNetwork(goProNetwork) sebelum pemindahan dan bindProcessToNetwork(null) selepasnya. Laluan GoPro berasaskan Dart tulen tiada setara — dan ia cenderung berfungsi juga kerana OS secara amnya menghalakan alamat tetap 10.5.5.9 ke AP. Tetapi ia adalah bahagian seni bina yang paling rapuh, dan pada sesetengah peranti/versi Android ia adalah perkara pertama yang rosak. Jika anda menghantar ini, tambahkan ikatan rangkaian — ia adalah satu-satunya tempat di mana kekal 100% Dart menjejaskan kebolehpercayaan anda.

Pengajaran yang diperoleh

  1. Anda tidak memerlukan BLE. Jika pengguna boleh menyertai AP, API HTTP Open GoPro memberikan anda kawalan penuh dan pemindahan pantas dalam Dart tulen.
  2. Kesan melalui probe TCP, bukan imbasan SSID — tiada kebenaran lokasi, tiada kod platform.
  3. Keep-alive setiap 30 s atau sesi tamat. Anggap Keep-alive yang gagal sebagai isyarat putus sambungan anda.
  4. Biarkan kamera membetulkan anda. Gelung pemulihan diri 403-available_options lebih baik daripada memodelkan keseluruhan matriks tetapan GoPro.
  5. Penukaran mod adalah mesin keadaan — selaraskannya, hentikan rakaman dahulu, hormati kelewatan peralihan lensa, dan percayai keadaan yang diinginkan berbanding keadaan yang di-poll.
  6. Fail iringan dan keanehan .360 adalah di mana masalah tersembunyi — penamaan semula LRV, tiada titik akhir lakaran kecil, pemotongan dual-fisheye.
  7. Dua "gotchas" infrastruktur akan secara senyap menenggelamkan anda: konfigurasi trafik cleartext, dan (terutamanya) mengikat proses kepada rangkaian kamera supaya permintaan tidak terkeluar melalui selular.

Hasilnya: telefon yang bersambung ke GoPro melalui WiFi, mengendalikan setiap kawalan, dan menstrimkan gigabait rakaman daripadanya dengan kemajuan langsung — dan hampir kesemuanya hanyalah Dart yang tersusun rapi bercakap dengan http://10.5.5.9:8080.
 

FlutterGoProHTTP APIStreaming
Saurav Kumar Gupta's image

Tentang Penulis

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

Building innovative AI-powered solutions and helping businesses transform through cutting-edge technology.

Ingin mengetahui lebih lanjut?

Hubungi kami untuk membincangkan bagaimana kami boleh membantu melaksanakan penyelesaian ini untuk perniagaan anda.

Hubungi Kami

Soalan Lazim

Yes. Once the phone is connected to the GoPro's WiFi network, the Open GoPro HTTP API allows you to control the camera, change settings, list media, and download files using standard HTTP requests.

GoPro automatically ends inactive WiFi sessions after about 60 seconds. Sending periodic keep-alive requests maintains the connection and prevents unexpected disconnections during camera control or file transfers.

Flutter apps can use the Open GoPro HTTP API with Dio for streaming downloads, enable Turbo Transfer for faster speeds, and track download progress in real time.

A 403 response indicates the requested setting isn't valid for the camera's current mode. The API also returns supported options, allowing the application to adapt automatically.

On Android, requests may be routed over cellular instead of the GoPro's WiFi network. Binding the app to the GoPro network improves connection reliability during camera control and media transfers.

Comments (0)

Share your thoughts and join the conversation

Leave a Comment

Your email will not be published

No comments yet

Be the first to share your thoughts!