Building Cross-Platform Video Call Apps with Flutter and ZEGO SDK

Modern real-time communication demands meet cross-platform versatility — Flutter + ZEGO SDK delivers just that. Leveraging Flutter’s single-codebase multidevice deployment and ZEGO’s optimized AV engine, developers can rapidly build lightweight, low-latency video calling apps for mobile, desktop, and web.

1. Prerequisites & Environment Setup

Ensure the following minimum requirements for target platforms:

  • Flutter ≥ 2.0
  • iOS ≥ 12.0 (prefer real device)
  • Android ≥ 4.4 (prefer real device, enable USB debugging)
  • Windows ≥ 8
  • macOS ≥ 10.14
  • Web: Chrome ≥58, Firefox ≥56, Safari ≥11, Opera ≥45, Blink-based QQ/360 browser
  • Linux: Debian ≥10, Ubuntu 20.04/22.04/24.04 LTS

Configure a supported IDE:

  • Android Studio: Enable Flutter plugin and set SDK path
  • VS Code: Install Flutter & Dart extensions

Run flutter doctor to install missing dependencies.

2. ZEGO Console Setup

  1. Sign up on ZEGO Cloud and log into the Console.
  2. Click Create Project → choose Real-Time Audio & Video.
  3. Copy the generated AppID (UUID format).
  4. Use the 24-hour temporary Token for development/testing (not production).

3. Integrate ZEGO Express SDK in Flutter

3.1 Create or Update Your Project

Generate a new project or open an existing one:

flutter create video_chat_app

3.2 Add SDK Dependency

Edit pubspec.yaml to include:

dependencies:
  flutter:
    sdk: flutter
  zego_express_engine: ^3.10.3

Then run:

flutter pub get

3.3 Configure Native Permissions

iOS (lib/ios/Runner/Info.plist):

<key>NSCameraUsageDescription</key>
<string>Camera access is required for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Mic access is needed for real-time audio</string>

Android (android/app/src/main/AndroidManifest.xml):

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="true"/>

Dynamic permission request (Android):

// In your Activity class
private val CAMERA_MIC_PERMISSIONS = arrayOf(
    Manifest.permission.CAMERA,
    Manifest.permission.RECORD_AUDIO
)
private const val PERMISSION_REQUEST_CODE = 101

override fun requestPermissionsIfRequired() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val missing = CAMERA_MIC_PERMISSIONS.filterNot {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
        if (missing.isNotEmpty()) {
            requestPermissions(missing.toTypedArray(), PERMISSION_REQUEST_CODE)
        }
    }
}

4. Core Implementation Steps

4.1 Initialize Engine

import 'package:zego_express_engine/zego_express_engine.dart';

final appID = 1234567890;      // decimal ID from console
final appSign =
    '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; // 64-char hex

ZegoEngineProfile profile = ZegoEngineProfile(
  appID,
  ZegoScenario.General,
  appSign: appSign,
  enablePlatformView: true,
);

ZegoExpressEngine.createEngineWithProfile(profile);

4.2 Join a Room

final user = ZegoUser(id: 'alice', name: 'Alice');
final roomConfig = ZegoRoomConfig.defaultConfig()
  ..isUserStatusNotify = true;

ZegoExpressEngine.instance.loginRoom('lobby', user, config: roomConfig);
Listen for Room Changes
ZegoExpressEngine.onRoomStateUpdate = (roomId, state, code, data) {
  print('Room $roomId state: $state, code: $code');
};

ZegoExpressEngine.onRoomUserUpdate = (roomId, type, users) {
  if (type == ZegoUpdateType.Add) {
    print('Users joined: ${users.map((u) => u.id).join(", ")}');
  }
};

ZegoExpressEngine.onRoomStreamUpdate = (roomId, type, streams) {
  if (type == ZegoUpdateType.Add) {
    streams.forEach((stream) => startRemotePlayback(stream.streamID));
  }
};

4.3 Start Publishing Local Stream

ZegoExpressEngine.instance.startPublishingStream('local_stream_alice');

ZegoExpressEngine.onPublisherStateUpdate = (streamId, state, code, data) {
  if (state == ZegoPublisherState.Error) {
    print('Publish failed: $code on $streamId');
  }
};

4.4 Play Remote Streams

Canvas setup (cross-platform):

Widget? remoteViewWidget;
int? remoteViewId;

void startRemotePlayback(String streamId) async {
  remoteViewWidget = await ZegoExpressEngine.instance.createCanvasView((id) {
    remoteViewId = id;
    final canvas = ZegoCanvas.view(id);
    ZegoExpressEngine.instance.startPlayingStream(streamId, canvas: canvas);
  });
}

Destroy view after use:

if (remoteViewId != null) {
  ZegoExpressEngine.instance.destroyCanvasView(remoteViewId!);
  remoteViewId = null;
}
Stream playback listener
ZegoExpressEngine.onPlayerStateUpdate = (streamId, state, code, data) {
  if (state == ZegoPlayerState.Playing) {
    print('Playing stream $streamId');
  }
};

4.5 Manage Lifecycle

  • Stop publishing: stopPublishingStream()
  • Stop preview: stopPreview(); destroyCanvasView(previewId)
  • Stop playing: stopPlayingStream(streamId)
  • Leave room: logoutRoom(roomId)
  • Release engine: ZegoExpressEngine.destroyEngine()

5. Testing & Debug

  • Run on real device(s) or simulator/emulator.
  • Use ZEGO’s Web test page: enter same AppID, RoomID, and Token to interop with mobile/desktop clients.
  • Verify audio/video in both directions. Check console for error-level logs if issues arise.

Thẻ: Flutter zegocloud webrtc audio-video-streaming realtime-communication

Đăng vào ngày 7 tháng 6 lúc 06:52