MicrocosmWorksNag-iinobasyon at Nagdidisenyo ng Digital Cosmos
Tungkol Sa AminMakipag-ugnayan
MicrocosmWorksNagpapabago at Nagdidisenyo ng Digital Cosmos

Nagbibigay ng mga solusyong IT na mahalaga. Kami ay masigasig sa teknolohiya, seguridad, at pagtulong sa mga negosyo na lumago sa pamamagitan ng maaasahan, makabagong IT infrastructure.

[email protected]
+91 7011868196
New Delhi, India

Sentro ng Paglago ng AI

AI HubInobasyon ng StartupPampabilis ng Negosyo

Mga Solusyon

Lahat ng SolusyonMga Wellness at Fitness AppsAI Video PlatformPag-unlad ng AI Agent

Mga Mapagkukunan

Mga PananawMga Gabay sa IndustriyaMga Plano ng PaggamitMga Pattern ng ArkitekturaMga Pag-aaral ng Kaso

Kumpanya

Tungkol sa AminMakipag-ugnayanAng Aming Gawain

Mga Serbisyo

Digital na PagkonsultaImprastraktura ng CloudPag-unlad ng SaaSPag-unlad ng AITeknolohiya ng Video
Pag-unlad ng ERPPagpapasadya ng ZohoPag-unlad ng OdooPagsasama ng SalesforcePag-unlad ng Custom na CRM
Pagsasama ng QuickBooksMga Solusyon sa IoTPag-unlad ng Blockchain
Pagkonsulta sa CybersecuritySuporta sa IT - L3

© 2026 MicrocosmWorks. Lahat ng karapatan ay nakalaan.

Patakaran sa PagkapribadoMga Tuntunin ng Serbisyo
Bumalik sa mga Pananaw
AI Development

Pagkontrol ng GoPro sa WiFi sa Flutter: Ang Open GoPro HTTP API, Keep-Alives, at Streaming File Transfer

Kumokonekta sa isang GoPro sa WiFi mula sa Flutter gamit ang Open GoPro HTTP API, na may keep-alives at streamed file transfer.

Saurav Kumar Gupta's image Saurav Kumar Gupta
•
July 17, 2026
•
Na-update 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

Walang SDK, walang BLE pairing dance — ang nakapirming IP lang ng camera, isang Dio client, at ilang "gotcha" na tahimik na sisira sa iyong araw kung hindi mo alam ang tungkol sa mga ito.

Bakit WiFi, hindi Bluetooth?

Karamihan sa mga "connect to a GoPro" na tutorial ay nagsisimula sa Bluetooth Low Energy: i-scan, i-pair, magpalitan ng GATT characteristics, pagkatapos ay gamitin ang BLE para i-on ang WiFi, pagkatapos ay i-transfer pa rin sa WiFi. Gumagana ito, ngunit maraming proseso — at ang BLE ay mabagal at maselan para sa bagay na talagang gusto ng mga user, na kontrolin ang camera at mabilis na kumuha ng footage mula dito.

Kaya sa app na ito ay tuluyan naming nilalaktawan ang BLE. Ang user ay kumokonekta sa GoPro's WiFi access point mula sa settings ng kanilang telepono, at mula sa puntong iyon, lahat ay simpleng HTTP laban sa Open GoPro API sa isang nakapirming address:

http://10.5.5.9:8080

Ang kontrol ng camera, mga setting, paglilista ng media, at pag-download ng file ay pawang mga HTTP GET request sa IP na iyon. Ang buong integrasyon ay 100% Dart gamit ang dio package — zero native Kotlin/Swift. Iyan ang pangunahing punto, at tulad ng makikita natin sa huli, ito rin ang iisang lugar kung saan mahina ang disenyo.

Ang daloy:

kumonekta sa GoPro WiFi AP (phone settings)
   → tukuyin kung naaabot ang camera (TCP probe sa 10.5.5.9:8080)
   → kumonekta + tukuyin ang modelo (GET /gopro/camera/state)
   → simulan ang keep-alive (30s) + status poll (5s) timers
   → kontrol: shutter / mode / settings
   → ilista ang media (GET /gopro/media/list)
   → i-download gamit ang turbo + progress (dio.download)

1. Pagtukoy sa camera nang walang WiFi-scan permissions

Ang unang instinct ay i-enumerate ang mga WiFi SSID at hanapin ang isa na nagsisimula sa GoPro. Huwag — iyan ay magdadala ng location permissions at platform-specific WiFi-scan API.

Ang GoPro ay laging nasa parehong IP, kaya ang pagtukoy ay simple lang: makapagbukas ba ako ng TCP socket sa 10.5.5.9:8080? Walang HTTP, walang permissions, walang SSID parsing.

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 ay nagpo-poll bawat 2 segundo at nagbro-broadcast ng mga pagbabago sa state sa isang stream. Ang connection screen ay nagsu-subscribe at nagiging "connected" sa sandaling magbukas ang socket. Dahil walang tunay na device discovery, ang serbisyo ay gumagawa ng virtual na CameraDevice (batay sa nakapirming IP) upang ang iba pang abstraction ng camera ay hindi na kailangang malaman ang pagkakaiba.

2. Ang HTTP client: Dio at ang Open GoPro endpoints

Ang client ay isang manipis na Dio wrapper. Tandaan ang maikling connect timeout (nasa isang LAN ka — kung mabagal, may sira) at isang mapagbigay na receive timeout para sa mga control call:

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'},
  ));
}

Ang Open GoPro API ay nakakatuwang uniform: lahat ay isang HTTP GET, kahit ang mga command at pagbabago sa settings. Narito ang mga endpoint na talagang ginagamit ng app na ito:

LayuninMethod + PathQuery
Simulan ang shutter (record / photo)GET /gopro/camera/shutter/start—
Ihinto ang shutterGET /gopro/camera/shutter/stop—
Keep-aliveGET /gopro/camera/keep_alive—
Buong state (status + settings)GET /gopro/camera/state—
Magtakda ng preset groupGET /gopro/camera/presets/set_groupid (1000/1001/1002)
Baguhin ang isang settingGET /gopro/camera/settingsetting, option
Listahan ng mediaGET /gopro/media/list—
Mag-download ng fileGET /videos/DCIM/{dir}/{file}(bytes)
Thumbnail / screennailGET /gopro/media/thumbnail / .../screennailpath
Turbo transferGET /gopro/media/turbo_transferp (1/0)
Burahin ang .360 group (legacy)GET /gp/gpControl/command/storage/delete/groupp

Isang magandang detalye: parehong umiiral ang modernong /gopro/... family at ang legacy na /gp/gpControl/... family. Ang pagtanggal ng chaptered.360/GS group files, na hindi kayang hawakan ng bagong API nang malinis, ay ang tanging sitwasyon kung saan kami gumagamit ng lumang endpoint.

Ang 403 self-healing pattern (ang pinakamalinis na trick dito)

Hindi mo malalaman nang maaga kung aling mga setting ang valid sa kasalukuyang mode ng camera — ang resolution na legal sa 16:9 ay illegal sa 9:16, ang mga opsyon sa FPS ay depende sa resolution, at iba pa. Sinasabi sa iyo ng GoPro ito sa mahirap na paraan: ibinabalik nito ang HTTP 403 na may listahan ng mga opsyong dapat ay valid, at HTTP 500 kapag sandali lang itong abala.

Kaya pinaparse namin ang 403 body sa isang typed error at sinusubukan muli ang 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;
    }
  }
}

Ang mga iniulat na opsyon ng camera ay ginagamit ng service layer upang i-rewrite ang sarili nitong capability list kung makakita ito ng SettingRejectedError. Para sa bawat modelo, nagsisimula kami sa isang optimistic, hard-coded capability table at pinapayagan ang camera na iwasto kami sa buong runtime. Mas praktikal at self-correcting ito kaysa sa pagtatangkang gayahin ang buong settings matrix ng GoPro nang maaga.

3. Keep-alive: ang timer na hindi mo dapat kalimutan

Pagkalipas ng humigit-kumulang 60 segundo ng inactivity, tatapusin ng GoPro WiFi AP ang iyong session. Tahimik na mamamatay ang iyong perpektong koneksyon sa gitna ng isang session kung hindi mo ito gagawing aktibo. Kaya, magsisimula kami ng dalawang timer sa pagkonekta:

  • Keep-alive bawat 30 s → GET /gopro/camera/keep_alive
  • Status poll bawat 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
  }
}

Ang isang failed keep-alive ay nagsisilbing disconnection signal din namin — ito ang pinakamapagkakatiwalaang paraan upang mapansin na lumabas ang user sa saklaw ng WiFi.

4. Kontrol ng camera: shutter, mode, at ang MAX 2 lens dance

Ang shutter ay ang madaling bahagi — simulan, maghintay ng 500 ms, i-re-poll ang state. Ang pagkuha ng larawan ay ang parehong shutter/start; kung makakakuha ka ng video o larawan ay depende sa kasalukuyang mode.

Ang paglipat ng mode ay kung saan nagiging interesante, dahil ang isang "mode" sa isang GoPro MAX 2 ay talagang dalawang setting: isang preset group (Video = 1000, Photo = 1001, Timelapse = 1002) plus isang lens (Setting 194: 0 = single-lens/HERO, 1 = 360). Ang paggawa nito nang tama ay nangailangan ng ilang hindi halatang panuntunan na inilagay sa code:

  • I-serialize ang mga pagbabago sa mode. Ang mabilis na pag-tap sa sub-mode ay pinipila sa pamamagitan ng isang Completer chain upang hindi sila magkapareho ng estado.
  • Ihinto muna ang shutter. Tinatanggihan ng Firmware ang mga pagbabago sa preset na may HTTP 400 habang nagre-record, kaya pinipigilan namin ang encoding bago magpalit.
  • Mahalaga ang order at mga pagkaantala. Sa MAX 2: itakda muna ang preset group (+400 ms), pagkatapos ang lens sa pamamagitan ng Setting 194 (+1500 ms). Totoo ang 1.5-segundong paghinto na iyan — ito ang paglipat ng physical lens assembly.
  • Magtiwala sa intended state, hindi sa polled state. Sinusubaybayan namin ang _lastIntendedIs360 / _lastIntendedPresetGroup at ikinukumpara laban sa mga iyon, dahil ang 5-segundong status poll ay maaaring magbigay sa iyo ng lumang snapshot sa gitna ng transisyon.

Mayroon ding talagang mapanlinlang na quirk ng GoPro na dapat banggitin: ang mga resolution ID ay nakasalalay sa aspect ratio. Ang 4K ay opsyon 1 sa 16:9, 109 sa 9:16, at 112 sa 4:3. Kaya pagkatapos baguhin ang aspect ratio, muli kaming nagtatanong ng mga kakayahan — ang parehong ID ay mangangahulugan ng ibang resolution.

5. Paglilista ng media

Ang GET /gopro/media/list ay nagbabalik ng isang directory-grouped structure na may sikat na maikling keys: d = directory, fs = files, n = filename, s = size, cre/mod = unix-second timestamps, g = group id, glrv = ang low-res-proxy size.

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)); // newest first

Ang uri ng media ay iniuugnay mula sa filename prefix + extension: GS = 360 video, GX = HERO video, GT = timelapse, .360 = spherical video, .GPR/.RAW = raw photo. Ang mga prefix na ito ay mahalaga sa bandang huli, dahil ang mga .360 na file ay kumikilos nang iba kumpara sa iba.

6. Streaming downloads: turbo, progress, at companion files

Ang aktwal na paglilipat ng byte ay dio.download() direkta sa isang file path, na may mahabang receive timeout dahil ang isang 1 GB .360 clip ay tumatagal ng matagal:

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)),
  );
}

Sa paligid ng core na iyon, ino-orchestrate ng transfer service ang mga detalye sa totoong mundo:

Turbo transfer. Bago mag-download, isina-aktibo namin ang turbo mode (turbo_transfer?p=1) — Lumipat ang GoPro sa 5 GHz WiFi para sa kapansin-pansing mas mabilis na paglilipat — at pinapatay namin ito sa isang finally block upang laging malinis, kahit na may error.

Progress + bilis, na throttled. Ang pag-compute ng bilis ng transfer sa bawat onReceiveProgress callback ay magastos at magulo, kaya muli lang namin itong kino-compute bawat 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));
}

Ang pag-cancel ay isang cooperative _isCancelled flag; sa pag-cancel, binubura namin ang partial file at naglalabas ng cancelled event.

Companion files. Ito ang bahaging hindi mo mahahanap sa docs. Para sa isang single-lens .MP4, kinukuha din namin ang .LRV (isang low-res proxy na nire-record ng GoPro kasama ng buong video) at ang .THM thumbnail, pagkatapos ay i-index ang LRV para sa mabilis na in-app playback. May dalawang naming "gotcha" dito:

  • Ang HERO8+ ay nagpapalit ng pangalan ng proxy: Ang GX######.MP4 / GH######.MP4 ay nagiging GL######.LRV (ang pangalawang character ay nagiging L), na may same-name fallback para sa mga mas lumang camera.
  • Ang mga .360 file ay hindi sumusuporta sa thumbnail o screennail endpoints. Gumagamit kami ng .THM companion, at bilang huling paraan, dina-download ang file mismo. At dahil ang isang .360 ay dual-fisheye, kino-crop namin ang thumbnail sa front lens — sa isang background isolate sa pamamagitan ng compute(), kaya ang UI ay hindi nagja-jank.

Ang transfer state ay ipinapakita sa pamamagitan ng isang Riverpod notifier na nagsu-subscribe sa file at transfer streams, pinapanatili ang isang tapos na transfer na nakikita sa loob ng 2 segundo, at ipinapakita ang mga error sa loob ng 5. Mahalagang tandaan: ang kontrol at file-transfer ay gumagamit ng dalawang independiyenteng Dio client sa parehong camera, kaya maaaring tumakbo ang isang download habang nagpapatuloy ang status poll at keep-alive.

7. Dalawang "gotcha" na wala sa anumang tutorial

Ang Cleartext HTTP ay blocked by default sa modernong Android

Ang http://10.5.5.9:8080 ay plaintext, at bini-block ng Android 9+ ang cleartext traffic kaagad. Kailangan mo itong tahasang payagan:

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

kasama ang isang network_security_config.xml na ang base config ay nagpapahintulot ng cleartext (maaari mo pa ring ipatupad ang HTTPS para sa sarili mong cloud/backend domains). Kung hindi mo ito gawin, lahat ng tawag sa GoPro ay mabibigo na may nakakalitong error sa koneksyon.

Ang nawawalang network binding — ang mahinang punto ng disenyo

Narito ang tapat na bahagi. Sa modernong Android, kapag kumonekta ka sa GoPro AP, karaniwang pinapanatili ng telepono ang cellular bilang default na network dahil walang internet ang GoPro AP. Nangangahulugan iyan na ang isang HTTP request sa 10.5.5.9 ay maaaring dumaan sa cellular at mabigo — kahit na "konektado" ka sa camera.

Ang matatag na pag-aayos ay native: tawagan ang ConnectivityManager.bindProcessToNetwork(goProNetwork) bago mag-transfer at bindProcessToNetwork(null) pagkatapos. Walang katumbas ang isang pure-Dart GoPro path — at karaniwan itong gumagana pa rin dahil karaniwang niru-route ng OS ang nakapirming 10.5.5.9 address sa AP. Ngunit ito ang pinakamahinang bahagi ng arkitektura, at sa ilang device/bersyon ng Android, ito ang unang nabubura. Kung ilalabas mo ito, idagdag ang network binding — ito ang tanging lugar kung saan ang pagiging 100% Dart ay magdudulot ng problema sa reliability.

Mga natutunan

  • Hindi mo kailangan ng BLE. Kung makakakonekta ang user sa AP, binibigyan ka ng Open GoPro HTTP API ng buong kontrol at mabilis na paglilipat sa pure Dart.
  • Tukuyin sa pamamagitan ng TCP probe, hindi sa SSID scan — walang location permission, walang platform code.
  • Keep-alive bawat 30 s o mamamatay ang session. Ituring ang isang failed keep-alive bilang iyong disconnect signal.
  • Hayaan ang camera na iwasto ka. Mas mahusay ang 403-available_options self-healing loop kaysa sa pagmodelo ng buong settings matrix ng GoPro.
  • Ang paglipat ng mode ay isang state machine — i-serialize ito, ihinto muna ang pagre-record, igalang ang mga pagkaantala sa lens-transition, at magtiwala sa intended state kaysa sa polled state.
  • Ang mga companion file at .360 quirks ang mga hindi nakikita na problema — pagpapalit ng pangalan ng LRV, walang thumbnail endpoint, dual-fisheye cropping.
  • Dalawang infrastructure "gotcha" ang tahimik na magpapabagsak sa iyo: cleartext traffic config, at (lalo na) ang pag-bind ng proseso sa network ng camera upang ang mga request ay hindi makalabas sa cellular.

Ang benepisyo: isang telepono na kumokonekta sa isang GoPro sa pamamagitan ng WiFi, nagmamaneho ng bawat kontrol, at nagsu-stream ng gigabytes ng footage mula dito na may live progress — at halos lahat ng ito ay simpleng well-structured na Dart na nakikipag-usap sa http://10.5.5.9:8080.
 

FlutterGoProHTTP APIStreaming
Saurav Kumar Gupta's image

Tungkol sa May-akda

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

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

Gusto mo bang matuto pa?

Makipag-ugnayan sa amin upang talakayin kung paano namin makakatulong na ipatupad ang mga solusyong ito para sa iyong negosyo.

Makipag-ugnayan

Mga Madalas Itanong

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!