MicrocosmWorksInnovation und Architektur digitaler Kosmen
Ăśber unsKontakt
MicrocosmWorksInnovieren und Gestalten digitaler Kosmen

Bereitstellung von IT-Lösungen, die zählen. Wir sind leidenschaftlich für Technologie, Sicherheit und helfen Unternehmen, durch zuverlässige, innovative IT-Infrastruktur zu wachsen.

[email protected]
+91 7011868196
New Delhi, India

AI Wachstumszentrum

AI HubStartup-InnovationUnternehmensbeschleuniger

Lösungen

Alle LösungenWellness- & Fitness-AppsAI Video PlattformAI Agent Entwicklung

Ressourcen

EinblickeBranchenleitfädenAnwendungsfall-BlaupausenArchitektur-MusterFallstudien

Unternehmen

Ăśber unsKontaktUnsere Arbeit

Dienstleistungen

Digitale BeratungCloud-InfrastrukturSaaS-EntwicklungKI-EntwicklungVideotechnologie
ERP-EntwicklungZoho-AnpassungOdoo-EntwicklungSalesforce-IntegrationBenutzerdefinierte CRM-Entwicklung
QuickBooks-IntegrationIoT-LösungenBlockchain-Entwicklung
Cybersecurity-BeratungIT-Support - L3

© 2026 MicrocosmWorks. Alle Rechte vorbehalten.

DatenschutzrichtlinieNutzungsbedingungen
ZurĂĽck zu Einblicken
AI Development

Eine GoPro ĂĽber WiFi in Flutter steuern: Die Open GoPro HTTP API, Keep-Alives und Streaming-DateiĂĽbertragung

Verbindung zu einer GoPro ĂĽber WiFi von Flutter aus, unter Verwendung der Open GoPro HTTP API, mit Keep-Alives und gestreamter DateiĂĽbertragung.

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

Kein SDK, kein BLE-Pairing-Tanz — nur die feste IP der Kamera, ein Dio Client und eine Handvoll Fallstricke, die einem den Tag leise ruinieren, wenn man sie nicht kennt.

Warum WiFi, nicht Bluetooth?

Die meisten Tutorials zum "Verbinden mit einer GoPro" beginnen mit Bluetooth Low Energy: scannen, koppeln, GATT-Merkmale austauschen, dann BLE verwenden, um WiFi einzuschalten, und dann sowieso über WiFi übertragen. Es funktioniert, ist aber sehr aufwendig — und BLE ist langsam und umständlich für das, was Benutzer wirklich wollen: die Kamera steuern und das Filmmaterial schnell herunterladen.

In dieser App verzichten wir daher vollständig auf BLE. Der Benutzer verbindet sich über die Telefoneinstellungen mit dem WiFi-Access Point der GoPro, und von diesem Zeitpunkt an erfolgt die gesamte Kommunikation über einfaches HTTP mit der Kamera-Open GoPro API unter einer festen Adresse:

http://10.5.5.9:8080

Kamerasteuerung, Einstellungen, Medienauflistung und Dateidownload sind alles HTTP GET-Anfragen an diese IP. Die gesamte Integration erfolgt zu 100% in Dart mit dem Dio-Paket — keinerlei natives Kotlin/Swift. Das ist die Schlagzeile, und wie wir am Ende sehen werden, ist es auch der einzige Punkt, an dem das Design fragil ist.

Der Ablauf:

GoPro WiFi AP beitreten (Telefoneinstellungen)
   → Kameraerreichbarkeit prĂĽfen (TCP-Probe an 10.5.5.9:8080)
   → Verbinden + Modell identifizieren (GET /gopro/camera/state)
   → Keep-Alive- (30s) + Statusabfrage-Timer (5s) starten
   → Steuerung: Auslöser / Modus / Einstellungen
   → Medien auflisten (GET /gopro/media/list)
   → Herunterladen mit Turbo + Fortschritt (dio.download)

1. Kameraerkennung ohne WiFi-Scan-Berechtigungen

Der erste Instinkt ist, WiFi SSIDs aufzulisten und nach einer zu suchen, die mit GoPro beginnt. Tun Sie das nicht — das zieht Standortberechtigungen und plattformspezifische WiFi-Scan-APIs nach sich.

GoPro ist immer unter derselben IP erreichbar, daher ist die Erkennung einfach: Kann ich einen TCP-Socket zu 10.5.5.9:8080 öffnen? Kein HTTP, keine Berechtigungen, kein 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);
    }
  }
}

Wir fragen das alle 2 Sekunden ab und senden Statusänderungen über einen Stream. Der Verbindungsbildschirm abonniert dies und wechselt zu "verbunden", sobald der Socket geöffnet wird. Da es keine echte Geräteerkennung gibt, synthetisiert der Dienst ein virtuelles CameraDevice (basierend auf der festen IP), sodass der Rest der Kameraabstraktion den Unterschied nicht kennen muss.

2. Der HTTP-Client: Dio und die Open GoPro Endpoints

Der Client ist ein schlanker Dio-Wrapper. Beachten Sie das kurze Verbindungs-Timeout (Sie befinden sich in einem LAN — wenn es langsam ist, ist es kaputt) und ein großzügiges Empfangs-Timeout für Steuerungsaufrufe:

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

Die Open GoPro API ist erfreulich einheitlich: Alles ist ein HTTP GET, sogar Befehle und Einstellungsänderungen. Hier sind die Endpoints, die diese App tatsächlich verwendet:

ZweckMethode + PfadAbfrage
Auslöser starten (Aufnahme / Foto)GET /gopro/camera/shutter/start—
Auslöser stoppenGET /gopro/camera/shutter/stop—
Keep-AliveGET /gopro/camera/keep_alive—
Vollständiger Status (Status + Einstellungen)GET /gopro/camera/state—
Preset-Gruppe festlegenGET /gopro/camera/presets/set_groupid (1000/1001/1002)
Eine Einstellung ändernGET /gopro/camera/settingsetting, option
MedienlisteGET /gopro/media/list—
Eine Datei herunterladenGET /videos/DCIM/{dir}/{file}(bytes)
Thumbnail / ScreennailGET /gopro/media/thumbnail / .../screennailpath
Turbo-ĂśbertragungGET /gopro/media/turbo_transferp (1/0)
.360-Gruppe löschen (Legacy)GET /gp/gpControl/command/storage/delete/groupp

Ein nettes Detail: Sowohl die moderne /gopro/...-Familie als auch die ältere /gp/gpControl/...-Familie koexistieren. Das Entfernen von chaptered.360/GS-Gruppendateien, die die neue API nicht sauber verarbeiten kann, ist die einzige Situation, in der wir auf den alten Endpoint zurückgreifen.

Das 403-Self-Healing-Muster (der cleverste Trick hier)

Man kann nicht im Voraus wissen, welche Einstellungen im aktuellen Modus der Kamera gültig sind — eine Auflösung, die in 16:9 zulässig ist, ist in 9:16 unzulässig, FPS-Optionen hängen von der Auflösung ab und so weiter. GoPro teilt Ihnen dies auf die harte Tour mit: Es gibt HTTP 403 mit der Liste der Optionen zurück, die gültig gewesen wären, und HTTP 500, wenn es nur kurzzeitig beschäftigt ist.

Daher parsen wir den 403-Body in einen typisierten Fehler und wiederholen den 500er:

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

Die von der Kamera gemeldeten Optionen werden von der Dienstschicht verwendet, um ihre eigene Fähigkeitsliste neu zu schreiben, wenn sie SettingRejectedError erkennt. Für jedes Modell beginnen wir mit einer optimistischen, fest codierten Fähigkeitstabelle und lassen die Kamera uns während der Laufzeit korrigieren. Das ist praktischer und selbstkorrigierender, als zu versuchen, die gesamte Einstellungsmatrix von GoPro im Voraus zu simulieren.

3. Keep-Alive: der Timer, den Sie nicht vergessen dĂĽrfen

Nach etwa 60 Sekunden Inaktivität beendet der GoPro WiFi AP Ihre Sitzung. Ihre perfekt funktionierende Verbindung stirbt leise mitten in einer Sitzung, wenn Sie sie nicht "anstupsen". Daher starten wir beim Verbindungsaufbau zwei Timer:

  • Keep-Alive alle 30 s → GET /gopro/camera/keep_alive
  • Statusabfrage alle 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
  }
}

Ein fehlgeschlagenes Keep-Alive ist auch unser Trennungssignal — es ist der zuverlässigste Weg, zu bemerken, dass der Benutzer den WiFi-Bereich verlassen hat.

4. Kamerasteuerung: Auslöser, Modus und der MAX 2 Objektivtanz

Der Auslöser ist der einfache Teil — starten, 500 ms warten, Status erneut abfragen. Die Fotoaufnahme ist der gleiche Auslöser/Start; ob Sie ein Video oder ein Foto erhalten, hängt vom aktuellen Modus ab.

Der Moduswechsel wird interessant, denn ein "Modus" bei einer GoPro MAX 2 sind eigentlich zwei Einstellungen: eine Preset-Gruppe (Video = 1000, Photo = 1001, Timelapse = 1002) plus ein Objektiv (Einstellung 194: 0 = Single-Lens/HERO, 1 = 360). Um dies richtig zu machen, mussten mehrere nicht offensichtliche Regeln in den Code integriert werden:

  1. Modusänderungen serialisieren. Schnelle Untermodus-Taps werden über eine Completer-Kette in eine Warteschlange gestellt, damit sie sich nicht gegenseitig in einen inkonsistenten Zustand versetzen können.
  2. Den Auslöser zuerst stoppen. Die Firmware lehnt Voreinstellungsänderungen mit HTTP 400 während der Aufnahme ab, daher stoppen wir die Kodierung proaktiv vor dem Umschalten.
  3. Reihenfolge und Verzögerungen sind wichtig. Bei MAX 2: Zuerst die Preset-Gruppe einstellen (+400 ms), dann das Objektiv über Einstellung 194 (+1500 ms). Diese 1,5-Sekunden-Pause ist real — es ist die Übergangsphase der physischen Objektivbaugruppe.
  4. Dem beabsichtigten Zustand vertrauen, nicht dem abgefragten Zustand. Wir verfolgen _lastIntendedIs360 / _lastIntendedPresetGroup und vergleichen diese, da die 5-Sekunden-Statusabfrage Ihnen eine veraltete Momentaufnahme mitten im Ăśbergang liefern kann.

Es gibt auch eine wirklich heimtückische GoPro-Eigenheit, die erwähnenswert ist: Auflösungs-IDs hängen vom Seitenverhältnis ab. 4K ist Option 1 in 16:9, 109 in 9:16 und 112 in 4:3. Daher fragen wir nach Änderung des Seitenverhältnisses die Fähigkeiten erneut ab — dieselbe ID würde sonst eine andere Auflösung bedeuten.

5. Medien auflisten

GET /gopro/media/list gibt eine nach Verzeichnissen gruppierte Struktur mit den bekanntermaßen knappen Schlüsseln zurück: d = Verzeichnis, fs = Dateien, n = Dateiname, s = Größe, cre/mod = Unix-Sekunden-Zeitstempel, g = Gruppen-ID, glrv = die Größe des Low-Res-Proxy.

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

Der Medientyp wird aus dem Dateinamenpräfix + der Erweiterung abgeleitet: GS = 360-Video, GX = HERO-Video, GT = Zeitraffer, .360 = sphärisches Video, .GPR/.RAW = Rohfoto. Diese Präfixe sind später wichtig, da sich .360-Dateien anders verhalten als alles andere.

6. Streaming-Downloads: Turbo, Fortschritt und Begleitdateien

Die eigentliche Byte-Ăśbertragung erfolgt ĂĽber dio.download() direkt in einen Dateipfad, mit einem langen Empfangs-Timeout, da ein 1 GB .360-Clip eine Weile dauert:

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

Um diesen Kern herum orchestriert der Ăśbertragungsdienst die Details der realen Welt:

Turbo-Übertragung. Vor dem Herunterladen schalten wir den Turbo-Modus ein (turbo_transfer?p=1) — GoPro wechselt für merklich schnellere Übertragungen zu 5 GHz WiFi — und wir schalten ihn in einem finally-Block wieder aus, damit er immer aufgeräumt wird, auch bei Fehlern.

Fortschritt + Geschwindigkeit, gedrosselt. Die Berechnung der Ăśbertragungsgeschwindigkeit bei jedem onReceiveProgress-Callback ist verschwenderisch und ruckelig, daher berechnen wir sie nur alle 500 ms neu:

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

Die Abbruchfunktion ist ein kooperatives _isCancelled-Flag; beim Abbruch löschen wir die Teildatei und senden ein "abgebrochen"-Ereignis aus.

Begleitdateien. Das ist der Teil, den Sie in den Docs nicht finden werden. Für ein Single-Lens .MP4 laden wir auch die .LRV (einen Low-Res-Proxy, den GoPro neben dem vollständigen Video aufzeichnet) und das .THM-Thumbnail herunter und indizieren dann die LRV für eine schnelle In-App-Wiedergabe. Zwei Fallstricke bei der Benennung schlagen hier zu:

  • HERO8+ benennt den Proxy um: GX######.MP4 / GH######.MP4 wird zu GL######.LRV (das zweite Zeichen wechselt zu L), mit einem Fallback auf den gleichen Namen fĂĽr ältere Kameras.
  • .360-Dateien unterstĂĽtzen die Thumbnail- oder Screennail-Endpoints ĂĽberhaupt nicht. Wir greifen auf die .THM-Begleitdatei zurĂĽck und laden im äuĂźersten Notfall die Datei selbst herunter. Und da eine .360-Datei Dual-Fisheye ist, beschneiden wir das Thumbnail auf das vordere Objektiv — auf einem Hintergrund-Isolate via compute(), damit die UI niemals ruckelt.

Der Übertragungsstatus wird über einen Riverpod-Notifier zugänglich gemacht, der die Datei- und Übertragungsströme abonniert, eine abgeschlossene Übertragung für 2 Sekunden sichtbar hält und Fehler für 5 Sekunden anzeigt. Bemerkenswert ist: Steuerung und Dateiübertragung verwenden zwei unabhängige Dio-Clients zur selben Kamera, sodass ein Download laufen kann, während Statusabfrage und Keep-Alive weiterlaufen.

7. Zwei Fallstricke, die in keinem Tutorial zu finden sind

Klartext-HTTP wird standardmäßig auf modernem Android blockiert

http://10.5.5.9:8080 ist Klartext, und Android 9+ blockiert Klartext-Traffic standardmäßig. Sie müssen es explizit zulassen:

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

plus eine network_security_config.xml, deren Basiskonfiguration Klartext erlaubt (Sie können immer noch HTTPS für Ihre eigenen Cloud-/Backend-Domains erzwingen). Wenn Sie dies übersehen, schlägt jeder GoPro-Aufruf mit einem verwirrenden Verbindungsfehler fehl.

Die fehlende Netzwerkbindung — der fragile Punkt des Designs

Hier ist der ehrliche Teil. Bei modernem Android behält das Telefon, wenn Sie dem GoPro AP beitreten, normalerweise Mobilfunk als Standardnetzwerk bei, da der GoPro AP kein Internet hat. Das bedeutet, dass eine HTTP-Anfrage an 10.5.5.9 über Mobilfunk weitergeleitet werden und fehlschlagen kann — auch wenn Sie mit der Kamera "verbunden" sind.

Die robuste Lösung ist nativ: rufen Sie ConnectivityManager.bindProcessToNetwork(goProNetwork) vor Übertragungen und bindProcessToNetwork(null) danach auf. Ein reiner Dart GoPro-Pfad hat kein Äquivalent — und es funktioniert sowieso meistens, weil das OS die feste Adresse 10.5.5.9 generell an den AP routet. Aber es ist der anfälligste Teil der Architektur, und auf einigen Geräten/Android-Versionen ist es das Erste, was kaputtgeht. Wenn Sie dies ausliefern, fügen Sie die Netzwerkbindung hinzu — es ist der einzige Punkt, an dem das Beibehalten von 100% Dart Sie Zuverlässigkeit kostet.

Gelernte Lektionen

  1. Sie benötigen kein BLE. Wenn der Benutzer dem AP beitreten kann, bietet Ihnen die Open GoPro HTTP API volle Kontrolle und schnelle Übertragungen in reinem Dart.
  2. Erkennung per TCP-Probe, nicht per SSID-Scan — keine Standortberechtigung, kein Plattformcode.
  3. Keep-Alive alle 30 s, sonst stirbt die Sitzung. Betrachten Sie ein fehlgeschlagenes Keep-Alive als Ihr Trennungssignal.
  4. Lassen Sie die Kamera Sie korrigieren. Die 403-available_options-Self-Healing-Schleife ist besser als die Modellierung der gesamten GoPro-Einstellungsmatrix.
  5. Der Moduswechsel ist eine Zustandsmaschine — serialisieren Sie ihn, stoppen Sie zuerst die Aufnahme, beachten Sie die Verzögerungen beim Objektivübergang und vertrauen Sie dem beabsichtigten Zustand mehr als dem abgefragten.
  6. Begleitdateien und .360-Eigenheiten sind die versteckten Fallstricke — LRV-Umbenennung, kein Thumbnail-Endpoint, Dual-Fisheye-Zuschneiden.
  7. Zwei Infrastruktur-Fallstricke werden Sie stillschweigend versenken: Klartext-Traffic-Konfiguration und (insbesondere) die Bindung des Prozesses an das Kameranetzwerk, damit Anfragen nicht ĂĽber Mobilfunk entweichen.

Die Belohnung: ein Telefon, das sich über WiFi mit einer GoPro verbindet, jede Steuerung übernimmt und Gigabytes an Filmmaterial mit Live-Fortschritt streamt — und fast alles davon ist einfach gut strukturierter Dart-Code, der mit http://10.5.5.9:8080 kommuniziert.
 

FlutterGoProHTTP APIStreaming
Saurav Kumar Gupta's image

Ăśber den Autor

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

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

Möchten Sie mehr erfahren?

Kontaktieren Sie uns, um zu besprechen, wie wir Ihnen bei der Implementierung dieser Lösungen für Ihr Unternehmen helfen können.

Kontakt aufnehmen

Häufig gestellte Fragen

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!