MicrocosmWorksInnovere og Arkitektere Digitale Kosmos
OmKontakt
MicrocosmWorksInnoverer og arkitekterer digitale kosmos

Leverer IT-løsninger, der betyder noget. Vi brænder for teknologi, sikkerhed og at hjælpe virksomheder med at vokse gennem pålidelig, innovativ IT-infrastruktur.

[email protected]
+91 7011868196
New Delhi, India

AI Væksthub

AI HubStartup-innovationVirksomhedsaccelerator

Løsninger

Alle løsningerSundhed & Fitness AppsAI VideoplatformAI Agentudvikling

Ressourcer

IndsigterIndustri GuiderBrugssag BlueprintsArkitektur MønstreCase Studier

Virksomhed

Om OsKontaktVores Arbejde

Tjenester

Digital RådgivningCloud InfrastrukturSaaS UdviklingAI UdviklingVideo Teknologi
ERP UdviklingZoho TilpasningOdoo UdviklingSalesforce IntegrationTilpasset CRM Udvikling
QuickBooks IntegrationIoT LøsningerBlockchain Udvikling
Cybersikkerhed RådgivningIT-support - L3

© 2026 MicrocosmWorks. Alle rettigheder forbeholdes.

PrivatlivspolitikServicevilkår
Tilbage til indsigter
AI Development

Styring af en GoPro over WiFi i Flutter: Den Open GoPro HTTP API, Keep-Alives og Streaming Filoverførsel

Forbindelse til en GoPro over WiFi fra Flutter ved brug af Open GoPro HTTP API, med keep-alives og streamet filoverførsel.

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

Ingen SDK, ingen BLE parringsdans — bare kameraets faste IP, en Dio client og en håndfuld faldgruber, der stille og roligt vil ødelægge din dag, hvis du ikke kender til dem.

Hvorfor WiFi, ikke Bluetooth?

De fleste "forbind til en GoPro"-vejledninger starter med Bluetooth Low Energy: scan, par, udveksl GATT-karakteristika, derefter brug BLE til at tænde WiFi, og overfør derefter via WiFi alligevel. Det virker, men det er meget omstændeligt — og BLE er langsomt og besværligt til det, brugerne faktisk ønsker, nemlig at styre kameraet og hurtigt trække optagelserne ud af det.

Så i denne app springer vi BLE helt over. Brugeren tilslutter sig GoPro's WiFi access point fra telefonens indstillinger, og derfra er alt almindelig HTTP mod kameraets Open GoPro API på en fast adresse:

http://10.5.5.9:8080

Kamerastyring, indstillinger, medieliste og filoverførsel er alt sammen HTTP GET-anmodninger til den IP. Hele integrationen er 100% Dart med dio-pakken — nul native Kotlin/Swift. Det er overskriften, og som vi vil se til sidst, er det også det ene sted, hvor designet er skrøbeligt.

Arbejdsgangen:

tilslut GoPro WiFi AP (telefonindstillinger)
   → registrer at kameraet er tilgængeligt (TCP probe til 10.5.5.9:8080)
   → tilslut + identificer model (GET /gopro/camera/state)
   → start keep-alive (30s) + status-polling (5s) timere
   → kontrol: udløser / tilstand / indstillinger
   → liste medier (GET /gopro/media/list)
   → download med turbo + fremskridt (dio.download)

1. Registrering af kameraet uden WiFi-scannings-tilladelser

Den første instinktive tanke er at opremse WiFi SSID'er og lede efter et, der starter med GoPro. Lad være — det trækker lokationstilladelser og platformspecifikke WiFi-scannings-API'er ind.

GoPro befinder sig altid på den samme IP, så detektering er blot: kan jeg åbne en TCP socket til 10.5.5.9:8080? Ingen HTTP, ingen tilladelser, ingen 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);
    }
  }
}

Vi poller dette hvert 2. sekund og udsender tilstandsændringer via en stream. Forbindelsesskærmen abonnerer og skifter til "forbundet" i det øjeblik socket'en åbnes. Fordi der ikke er nogen reel enheds-opdagelse, syntetiserer tjenesten en virtuel CameraDevice (baseret på den faste IP), så resten af kamera-abstraktionen ikke behøver at kende forskellen.

2. HTTP-klienten: Dio og Open GoPro-endepunkterne

Klienten er en tynd Dio-wrapper. Bemærk den korte forbindelsestimeout (du er på et LAN — hvis den er langsom, er den i stykker) og en generøs modtagelsestimeout for kontrolkald:

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

Den Open GoPro API er dejligt ensartet: alt er en HTTP GET, selv kommandoer og indstillingsændringer. Her er de endepunkter, denne app faktisk bruger:

FormålMetode + StiForespørgsel
Start udløser (optag / foto)GET /gopro/camera/shutter/start—
Stop udløserGET /gopro/camera/shutter/stop—
Keep-aliveGET /gopro/camera/keep_alive—
Fuld tilstand (status + indstillinger)GET /gopro/camera/state—
Indstil forudindstillet gruppeGET /gopro/camera/presets/set_groupid (1000/1001/1002)
Skift en indstillingGET /gopro/camera/settingsetting, option
MedielisteGET /gopro/media/list—
Download en filGET /videos/DCIM/{dir}/{file}(bytes)
Miniature / screennailGET /gopro/media/thumbnail / .../screennailpath
TurbooverførselGET /gopro/media/turbo_transferp (1/0)
Slet .360 gruppe (ældre)GET /gp/gpControl/command/storage/delete/groupp

En god detalje: både den moderne /gopro/...-familie og den ældre /gp/gpControl/...-familie eksisterer side om side. Fjernelse af chaptered.360/GS-gruppefiler, som den nye API ikke kan håndtere rent, er den eneste situation, hvor vi tyr til det ældre endepunkt.

403 selvhelbredelsesmønsteret (det smarteste trick her)

Du kan ikke på forhånd vide, hvilke indstillinger der er gyldige i kameraets nuværende tilstand — en opløsning, der er lovlig i 16:9, er ulovlig i 9:16, FPS-indstillinger afhænger af opløsning, og så videre. GoPro fortæller dig dette på den hårde måde: den returnerer HTTP 403 med listen over muligheder, der ville have været gyldige, og HTTP 500, når den blot er midlertidigt optaget.

Så vi parser 403-body'en til en typet fejl og forsøger 500 igen:

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

Kameraets rapporterede muligheder bruges af servicelaget til at omskrive sin egen kapacitetsliste, hvis den registrerer SettingRejectedError. For hver model starter vi med en optimistisk, hardcoded kapacitetstabel og lader kameraet korrigere os under hele kørslen. Det er mere praktisk og selvkorrigerende end at forsøge at simulere GoPro's samlede indstillingsmatrix på forhånd.

3. Keep-alive: timeren du ikke må glemme

Efter cirka 60 sekunders inaktivitet afslutter GoPro WiFi AP din session. Din helt fine forbindelse dør stille og roligt midt i en session, hvis du ikke "poker" den. Derfor starter vi to timere ved forbindelse:

  • Keep-alive hvert 30. s → GET /gopro/camera/keep_alive
  • Status polling hvert 5. s → GET /gopro/camera/state

void _startKeepAlive() {
  _keepAliveTimer?.cancel();
  _keepAliveTimer = Timer.periodic(
    GoProHttpConstants.keepAliveInterval,
    (_) => _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
  }
}

En mislykket keep-alive er også vores frakoblingssignal — det er den mest pålidelige måde at bemærke, at brugeren gik ud af WiFi-rækkevidde.

4. Kamerastyring: udløser, tilstand og MAX 2-linse-dansen

Udløseren er den nemme del — start, vent 500 ms, gen-poll tilstand. Fotooptagelse er den samme udløser/start; om du får en video eller et foto afhænger af den nuværende tilstand.

Tilstandsskifte er, hvor det bliver interessant, fordi en "tilstand" på en GoPro MAX 2 faktisk er to indstillinger: en forudindstillet gruppe (Video = 1000, Photo = 1001, Timelapse = 1002) plus en linse (Indstilling 194: 0 = enkelt-linse/HERO, 1 = 360). At få dette rigtigt krævede flere ikke-åbenlyse regler indlejret i koden:

  1. Serialiser tilstandsændringer. Hurtige sub-tilstands-tryk køes via en Completer-kæde, så de ikke kan "race" hinanden ind i en inkonsistent tilstand.
  2. Stop udløseren først. Firmware afviser forudindstillede ændringer med HTTP 400 under optagelse, så vi stopper proaktivt kodning, før vi skifter.
  3. Rækkefølge og forsinkelser er vigtige. På MAX 2: indstil forudindstillet gruppe først (+400 ms), derefter linsen via Indstilling 194 (+1500 ms). Den 1,5 sekunders pause er reel — det er den fysiske linsemontering, der skifter.
  4. Stol på den tilsigtede tilstand, ikke den poll-ede tilstand. Vi sporer _lastIntendedIs360 / _lastIntendedPresetGroup og differentierer mod dem, fordi den 5-sekunders status-polling kan give dig et forældet øjebliksbillede midt i en overgang.

Der er også en virkelig snedig GoPro-særhed, der er værd at nævne: opløsnings-ID'er afhænger af billedformatet. 4K er mulighed 1 i 16:9, 109 i 9:16 og 112 i 4:3. Så efter ændring af billedformatet genforespørger vi kapaciteter — det samme ID ville ellers betyde en anden opløsning.

5. Liste over medier

GET /gopro/media/list returnerer en mappe-grupperet struktur med berømt kortfattede nøgler: d = mappe, fs = filer, n = filnavn, s = størrelse, cre/mod = unix-sekund tidsstempler, g = gruppe-ID, glrv = størrelsen på low-res-proxy'en.

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

Medietype udledes af filnavnets præfiks + udvidelse: GS = 360 video, GX = HERO video, GT = timelapse, .360 = sfærisk video, .GPR/.RAW = råfoto. Disse præfikser betyder noget senere, fordi .360-filer opfører sig anderledes end alt andet.

6. Streaming downloads: turbo, fremskridt og ledsagerfiler

Den faktiske byteoverførsel er dio.download() direkte til en filsti, med en lang modtagelsestimeout, fordi et 1 GB .360-klip tager et stykke tid:

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

Omkring denne kerne orkestrerer overførselstjenesten de virkelige detaljer:

Turbooverførsel. Før download slår vi turbotilstand til (turbo_transfer?p=1) — GoPro skifter til 5 GHz WiFi for mærkbart hurtigere overførsler — og vi slår det fra i en finally-blok, så det altid bliver ryddet op, selv ved fejl.

Fremskridt + hastighed, droslet. At beregne overførselshastighed ved hvert onReceiveProgress-callback er spild og ustabilt, så vi genberegner det kun hvert 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));
}

Annullering er et kooperativt _isCancelled flag; ved annullering sletter vi den delvise fil og udsender en annulleret hændelse.

Ledsagerfiler. Dette er den del, du ikke finder i dokumentationen. For en enkeltlinse .MP4 trækker vi også .LRV (en lavopløsningsproxy GoPro optager sammen med den fulde video) og .THM-miniaturebilledet, og indekserer derefter LRV'en for hurtig afspilning i appen. To navngivningsfælder bider her:

  • HERO8+ omdøber proxyen: GX######.MP4 / GH######.MP4 bliver GL######.LRV (det andet tegn skifter til L), med en samme-navn-fallback for ældre kameraer.
  • .360-filer understøtter slet ikke miniature- eller screennail-endepunkterne. Vi falder tilbage til .THM-ledsageren, og som en sidste udvej downloader vi selve filen. Og fordi en .360 er dual-fisheye, beskærer vi miniaturebilledet til frontlinsen — på en baggrunds-isolate via compute(), så UI'en aldrig "janker".

Overførselstilstanden eksponeres via en Riverpod notifier, der abonnerer på fil- og overførselsstreams, holder en afsluttet overførsel synlig i 2 sekunder, og viser fejl i 5. Værd at bemærke: kontrol og filoverførsel bruger to uafhængige Dio clients til det samme kamera, så en download kan køre, mens status-polling og keep-alive fortsætter.

7. To faldgruber, der ikke er i nogen tutorial

Cleartext HTTP er blokeret som standard på moderne Android

http://10.5.5.9:8080 er plaintext, og Android 9+ blokerer cleartext-trafik som standard. Du skal eksplicit tillade det:

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

plus en network_security_config.xml, hvis basiskonfiguration tillader cleartext (du kan stadig tvinge HTTPS for dine egne cloud/backend-domæner). Glem dette, og hvert GoPro-kald mislykkes med en forvirrende forbindelsesfejl.

Den manglende netværksbinding — designets skrøbelige punkt

Her er den ærlige del. På moderne Android, når du tilslutter dig GoPro AP'en, beholder telefonen normalt mobildata som standardnetværk, fordi GoPro AP'en ikke har internetadgang. Det betyder, at en HTTP-anmodning til 10.5.5.9 kan blive rutet ud over mobildata og fejle — selvom du er "forbundet" til kameraet.

Den robuste løsning er native: kald ConnectivityManager.bindProcessToNetwork(goProNetwork) før overførsler og bindProcessToNetwork(null) efter. En ren Dart GoPro-sti har ingen ækvivalent — og det plejer at virke alligevel, fordi OS'et generelt dirigerer den faste 10.5.5.9-adresse til AP'en. Men det er den mest skrøbelige del af arkitekturen, og på nogle enheder/Android-versioner er det det første, der bryder sammen. Hvis du udgiver dette, tilføj netværksbindingen — det er det ene sted, hvor det koster dig pålidelighed at forblive 100% Dart.

Erfaringer

  1. Du behøver ikke BLE. Hvis brugeren kan tilslutte sig AP'en, giver Open GoPro HTTP API dig fuld kontrol og hurtige overførsler i ren Dart.
  2. Registrer via TCP-probe, ikke SSID-scanning — ingen lokationstilladelse, ingen platformskode.
  3. Keep-alive hvert 30. s, ellers dør sessionen. Behandl en mislykket keep-alive som dit frakoblingssignal.
  4. Lad kameraet korrigere dig. 403-available_options selvhelbredelses-loopet slår at modellere GoPro's komplette indstillingsmatrix.
  5. Tilstandsskifte er en tilstandsmaskine — serialiser det, stop optagelse først, respekter linse-overgangsforsinkelserne, og stol på den tilsigtede tilstand frem for den poll-ede tilstand.
  6. Ledsagerfiler og .360-særheder er, hvor "ligene er begravet" — LRV-omdøbning, intet miniature-endepunkt, dual-fisheye beskæring.
  7. To infrastruktur-faldgruber vil lydløst "sænke" dig: cleartext-trafikkonfiguration, og (især) binding af processen til kameranetværket, så anmodninger ikke undslipper over mobildata.

Udbyttet: en telefon, der forbinder til en GoPro over WiFi, styrer hver kontrol, og streamer gigabytes af optagelser fra den med live fremskridt — og næsten alt det er bare velstruktureret Dart, der taler til http://10.5.5.9:8080.
 

FlutterGoProHTTP APIStreaming
Saurav Kumar Gupta's image

Om forfatteren

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

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

Vil du lære mere?

Kontakt os for at diskutere, hvordan vi kan hjælpe med at implementere disse løsninger for din virksomhed.

Kom i Kontakt

Ofte stillede spørgsmål

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!