MicrocosmWorksІнновації та архітектура цифрового космосу
Про насКонтакт
MicrocosmWorksІнновації та архітектура цифрового космосу

Надаємо IT-рішення, які мають значення. Ми захоплені технологіями, безпекою та допомогою бізнесу зростати завдяки надійній, інноваційній IT-інфраструктурі.

[email protected]
+91 7011868196
New Delhi, India

Центр зростання AI

AI HubІнновації для стартапівПрискорювач для підприємств

Рішення

Всі рішенняДодатки для здоров'я та фітнесуAI відео платформаРозробка AI агентів

Ресурси

ІнсайтиГалузеві ПосібникиШаблони ВикористанняАрхітектурні ШаблониКейси

Компанія

Про НасКонтактНаша Робота

Послуги

Цифровий КонсалтингХмарна ІнфраструктураРозробка SaaSРозробка AIВідео Технології
Розробка ERPНалаштування ZohoРозробка OdooІнтеграція SalesforceРозробка Користувацьких CRM
Інтеграція QuickBooksРішення IoTРозробка Блокчейну
Консалтинг з КібербезпекиІТ Підтримка - L3

© 2026 MicrocosmWorks. Усі права захищено.

Політика КонфіденційностіУмови Обслуговування
Назад до інсайтів
AI Development

Керування GoPro через WiFi у Flutter: Open GoPro HTTP API, Keep-Alives та потокова передача файлів

Підключення до GoPro через WiFi з Flutter за допомогою Open GoPro HTTP API, з Keep-Alives та потоковою передачею файлів.

Saurav Kumar Gupta's image Saurav Kumar Gupta
•
July 17, 2026
•
Оновлено 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

Без SDK, без складнощів зі сполученням через BLE — лише фіксована IP-адреса камери, клієнт Dio та кілька підводних каменів, які тихо зіпсують вам день, якщо ви про них не знатимете.

Чому WiFi, а не Bluetooth?

Більшість посібників із "підключення до GoPro" починаються з Bluetooth Low Energy: сканування, сполучення, обмін характеристиками GATT, потім використання BLE для ввімкнення WiFi, а потім все одно передача через WiFi. Це працює, але це багато зайвих рухів — і BLE повільний та примхливий для того, що користувачі насправді хочуть: керувати камерою та швидко завантажувати з неї матеріал.

Отже, у цьому застосунку ми повністю пропускаємо BLE. Користувач приєднується до точки доступу WiFi GoPro через налаштування свого телефону, і з цього моменту все відбувається через звичайний HTTP до Open GoPro API камери за фіксованою адресою:

http://10.5.5.9:8080

Керування камерою, налаштування, список медіафайлів та завантаження файлів — все це HTTP GET запити на цю IP-адресу. Вся інтеграція на 100% написана на Dart з використанням пакету dio — жодного нативного Kotlin/Swift. Це головне, і, як ми побачимо наприкінці, це також єдине місце, де дизайн є крихким.

Послідовність дій:

приєднатися до точки доступу GoPro WiFi (налаштування телефону)
   → виявити доступність камери (TCP-проба до 10.5.5.9:8080)
   → підключитися + ідентифікувати модель (GET /gopro/camera/state)
   → запустити таймери Keep-alive (30 с) + опитування стану (5 с)
   → керування: затвор / режим / налаштування
   → вивести список медіафайлів (GET /gopro/media/list)
   → завантажити з турборежимом + прогресом (dio.download)

1. Виявлення камери без дозволів на сканування WiFi

Перший інстинкт — перерахувати WiFi SSID і шукати той, що починається з GoPro. Не робіть цього — це тягне за собою дозволи на визначення місцезнаходження та API для сканування WiFi, специфічні для платформи.

GoPro завжди знаходиться за однією і тією ж IP-адресою, тому виявлення полягає лише в тому, щоб запитати: чи можу я відкрити TCP-сокет до 10.5.5.9:8080? Без HTTP, без дозволів, без парсингу 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);
    }
  }
}

Ми опитуємо це кожні 2 секунди та транслюємо зміни стану через потік. Екран підключення підписується і перемикається на "підключено" в момент відкриття сокета. Оскільки реального виявлення пристроїв немає, сервіс синтезує віртуальний CameraDevice (з прив'язкою до фіксованої IP-адреси), щоб решта абстракції камери не потребувала знання різниці.

2. HTTP-клієнт: Dio та кінцеві точки Open GoPro

Клієнт — це тонка обгортка Dio. Зверніть увагу на короткий час очікування підключення (ви в локальній мережі — якщо повільно, значить, несправно) та великий час очікування прийому для керуючих викликів:

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

Open GoPro API чудово уніфікований: все є HTTP GET запитами, навіть команди та зміни налаштувань. Ось кінцеві точки, які цей застосунок фактично використовує:

ПризначенняМетод + ШляхЗапит
Запуск затвора (відео / фото)GET /gopro/camera/shutter/start—
Зупинка затвораGET /gopro/camera/shutter/stop—
Keep-aliveGET /gopro/camera/keep_alive—
Повний стан (статус + налаштування)GET /gopro/camera/state—
Встановити групу пресетівGET /gopro/camera/presets/set_groupid (1000/1001/1002)
Змінити налаштуванняGET /gopro/camera/settingsetting, option
Список медіафайлівGET /gopro/media/list—
Завантажити файлGET /videos/DCIM/{dir}/{file}(bytes)
Ескіз / знімок екранаGET /gopro/media/thumbnail / .../screennailpath
ТурбопередачаGET /gopro/media/turbo_transferp (1/0)
Видалити групу .360 (застаріле)GET /gp/gpControl/command/storage/delete/groupp

Приємна деталь: співіснують як сучасне сімейство /gopro/..., так і застаріле сімейство /gp/gpControl/.... Видалення згрупованих файлів chaptered.360/GS, які новий API не може обробляти коректно, є єдиною ситуацією, коли ми вдаємося до старого кінцевого пункту.

Шаблон самовідновлення 403 (найчистіший трюк тут)

Ви не можете заздалегідь знати, які налаштування є дійсними в поточному режимі камери — роздільна здатність, яка дозволена у 16:9, є недозволеною у 9:16, параметри FPS залежать від роздільної здатності тощо. GoPro повідомляє вам про це складним шляхом: вона повертає HTTP 403 зі списком опцій, які були б дійсними, та HTTP 500, коли вона просто тимчасово зайнята.

Отже, ми розбираємо тіло 403 у типізовану помилку та повторюємо спробу при 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;
        }
      }
    }
  }
}

Повідомлені камерою опції використовуються рівнем сервісу для перезапису власного списку можливостей, якщо він виявляє SettingRejectedError. Для кожної моделі ми починаємо з оптимістичної, жорстко закодованої таблиці можливостей і дозволяємо камері коригувати нас під час виконання. Це практичніше та самокоригуюче, ніж намагатися заздалегідь симулювати всю матрицю налаштувань GoPro.

3. Keep-alive: таймер, про який не можна забувати

Після приблизно 60 секунд бездіяльності точка доступу GoPro WiFi завершує вашу сесію. Ваше ідеальне з'єднання тихо згасає посеред сесії, якщо ви його не "підштовхнете". Таким чином, ми запускаємо два таймери при підключенні:

  • Keep-alive кожні 30 с → GET /gopro/camera/keep_alive
  • Опитування стану кожні 5 с → 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 також є нашим сигналом про роз'єднання — це найнадійніший спосіб помітити, що користувач вийшов за межі діапазону WiFi.

4. Керування камерою: затвор, режим та "танець" об'єктива MAX 2

Затвор — це легка частина: запуск, очікування 500 мс, повторне опитування стану. Зйомка фото — це той самий затвор/запуск; чи отримаєте ви відео або фото, залежить від поточного режиму.

Перемикання режимів стає цікавим, оскільки "режим" на GoPro MAX 2 — це насправді два налаштування: група пресетів (Video = 1000, Photo = 1001, Timelapse = 1002) плюс об'єктив (Setting 194: 0 = single-lens/HERO, 1 = 360). Правильна реалізація цього вимагала кількох неочевидних правил, закладених у код:

  1. Серіалізуйте зміни режимів. Швидкі натискання підрежимів ставляться в чергу через ланцюжок Completer, щоб вони не могли змагатися один з одним, призводячи до несумісного стану.
  2. Спочатку зупиніть затвор. Прошивка відхиляє зміни пресетів з HTTP 400 під час запису, тому ми завчасно зупиняємо кодування перед перемиканням.
  3. Порядок та затримки мають значення. На MAX 2: спочатку встановіть групу пресетів (+400 мс), потім об'єктив через Setting 194 (+1500 мс). Ця 1,5-секундна пауза є реальною — це перехід фізичної збірки об'єктива.
  4. Довіряйте передбачуваному стану, а не опитуваному. Ми відстежуємо _lastIntendedIs360 / _lastIntendedPresetGroup і порівнюємо з ними, оскільки 5-секундне опитування статусу може надати вам застарілий знімок під час переходу.

Існує також по-справжньому підступна особливість GoPro, на яку варто звернути увагу: ідентифікатори роздільної здатності залежать від співвідношення сторін. 4K — це опція 1 у 16:9, 109 у 9:16 та 112 у 4:3. Тому після зміни співвідношення сторін ми повторно запитуємо можливості — інакше той самий ідентифікатор означав би іншу роздільну здатність.

5. Список медіафайлів

GET /gopro/media/list повертає структуру, згруповану за директоріями, з відомо короткими ключами: d = directory, fs = files, n = filename, s = size, cre/mod = unix-second timestamps, g = group id, glrv = the 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

Тип медіафайлу визначається за префіксом імені файлу + розширенням: GS = 360 video, GX = HERO video, GT = timelapse, .360 = spherical video, .GPR/.RAW = raw photo. Ці префікси важливі пізніше, тому що файли .360 поводяться інакше, ніж усі інші.

6. Потокове завантаження: турбо, прогрес та супутні файли

Фактична передача байтів відбувається за допомогою dio.download() безпосередньо до файлового шляху, з тривалим тайм-аутом прийому, оскільки 1 ГБ .360 кліпу займає певний час:

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

Навколо цього ядра служба передачі організує реальні деталі:

Турбопередача. Перед завантаженням ми вмикаємо турбо-режим (turbo_transfer?p=1) — GoPro перемикається на 5 ГГц WiFi для помітно швидшої передачі — і вимикаємо його в блоці finally, щоб він завжди очищався, навіть у разі помилки.

Прогрес + швидкість, обмежені. Обчислення швидкості передачі на кожному зворотньому виклику onReceiveProgress є марним і призводить до перебоїв, тому ми переобчислюємо її лише кожні 500 мс:

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

Скасування — це кооперативний прапорець _isCancelled; при скасуванні ми видаляємо частковий файл і генеруємо подію скасування.

Супутні файли. Це та частина, яку ви не знайдете в документації. Для однооб'єктивного .MP4 ми також завантажуємо .LRV (проксі-файл низької роздільної здатності, який GoPro записує разом із повним відео) та ескіз .THM, а потім індексуємо LRV для швидкого відтворення в застосунку. Тут є два підводних камені, пов'язані з іменами:

  • HERO8+ перейменовують проксі: GX######.MP4 / GH######.MP4 стає GL######.LRV (другий символ змінюється на L), з резервним варіантом тієї ж назви для старих камер.
  • .360 файли взагалі не підтримують кінцеві точки для мініатюр або знімків екрана. Ми вдаємося до супутнього файлу .THM, а в крайньому випадку завантажуємо сам файл. І оскільки .360 є двооб'єктивним "риб'ячим оком", ми обрізаємо мініатюру до переднього об'єктива — у фоновому ізоляті через compute(), щоб UI ніколи не гальмував.

Стан передачі виставляється через Riverpod notifier, який підписується на потоки файлів і передачі, зберігає завершену передачу видимою протягом 2 секунд і відображає помилки протягом 5. Варто зазначити: керування та передача файлів використовують два незалежні Dio клієнти до однієї камери, тому завантаження може відбуватися, поки триває опитування стану та Keep-alive.

7. Два підводні камені, яких немає в жодному посібнику

Cleartext HTTP заблоковано за замовчуванням на сучасному Android

http://10.5.5.9:8080 — це відкритий текст, а Android 9+ за замовчуванням блокує cleartext трафік. Вам потрібно явно дозволити його:

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

плюс network_security_config.xml, чия базова конфігурація дозволяє cleartext (ви все ще можете примусово використовувати HTTPS для своїх власних хмарних/бекенд доменів). Пропустіть це, і кожен виклик GoPro завершиться помилкою з незрозумілою помилкою підключення.

Відсутнє мережеве прив'язування — крихке місце дизайну

Ось чесна частина. На сучасному Android, коли ви приєднуєтеся до точки доступу GoPro, телефон зазвичай зберігає стільниковий зв'язок як мережу за замовчуванням, оскільки точка доступу GoPro не має доступу до Інтернету. Це означає, що HTTP-запит до 10.5.5.9 може бути направлений через стільникову мережу і зазнати невдачі — навіть якщо ви "підключені" до камери.

Надійне рішення є нативним: викличте ConnectivityManager.bindProcessToNetwork(goProNetwork) перед передачею та bindProcessToNetwork(null) після. Чистий Dart шлях GoPro не має еквівалента — і він все одно, як правило, працює, оскільки ОС зазвичай маршрутизує фіксовану адресу 10.5.5.9 до точки доступу. Але це найкрихкіша частина архітектури, і на деяких пристроях/версіях Android це перше, що ламається. Якщо ви це розгортаєте, додайте мережеве прив'язування — це єдине місце, де збереження 100% Dart коштує вам надійності.

Винесені уроки

  1. Вам не потрібен BLE. Якщо користувач може приєднатися до точки доступу, Open GoPro HTTP API надає вам повний контроль та швидку передачу даних у чистому Dart.
  2. Виявляйте за допомогою TCP-проби, а не сканування SSID — без дозволу на визначення місцезнаходження, без платформо-залежного коду.
  3. Використовуйте Keep-alive кожні 30 с, інакше сесія загине. Розглядайте невдалий Keep-alive як сигнал про роз'єднання.
  4. Дозвольте камері коригувати вас. Цикл самовідновлення 403-available_options краще, ніж моделювання всієї матриці налаштувань GoPro.
  5. Перемикання режимів — це кінцевий автомат: серіалізуйте його, спочатку зупиніть запис, дотримуйтесь затримок переходу об'єктива та довіряйте передбачуваному стану, а не опитуваному.
  6. Супутні файли та особливості .360 — це місце, де приховані "тіла": перейменування LRV, відсутність кінцевої точки для мініатюр, обрізка подвійного "риб'ячого ока".
  7. Два інфраструктурні підводні камені, які непомітно потоплять вас: конфігурація cleartext трафіку та (особливо) прив'язування процесу до мережі камери, щоб запити не відправлялися через стільниковий зв'язок.

Результат: телефон, який підключається до GoPro через WiFi, керує всіма елементами керування та транслює гігабайти відзнятого матеріалу з відстеженням прогресу в реальному часі — і майже все це просто добре структурований Dart, який спілкується з http://10.5.5.9:8080.
 

FlutterGoProHTTP APIStreaming
Saurav Kumar Gupta's image

Про автора

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

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

Бажаєте дізнатися більше?

Зв'яжіться з нами, щоб обговорити, як ми можемо допомогти впровадити ці рішення для вашого бізнесу.

Зв'яжіться з нами

Часті запитання

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!