Skip to content

Repository files navigation

flutter_acs_calling

CI License: MIT Platforms Flutter

Video calling for Flutter on Azure Communication Services — with native Android screen sharing and a self-contained web bridge.

Microsoft provides no official Flutter SDK for ACS calling. This plugin fills that gap by bridging the native Android Calling SDK and the ACS Web JS SDK behind a single Dart API — built for, and extracted from, a production telehealth application where remote optometrists join field clinics by video.

Features

  • ✅ Video calling using Azure Communication Services
  • ✅ Screen sharing on Android (native MediaProjection implementation, Android 14/15 compliant)
  • ✅ Adaptive video quality based on network conditions
  • ✅ Media controls (camera, microphone, speaker, camera switch)
  • ✅ Incoming-call popup with ringtone and call history
  • ✅ Room-based calling
  • ✅ Real-time video streaming
  • ✅ Web support via InAppWebView (self-contained vendored ACS Web SDK)

Platforms

  • ✅ Android (API 23+)
  • ✅ Web
  • ⏳ iOS — not yet implemented (roadmap)

Quick start

git clone https://github.com/guss1215/flutter_acs_calling.git
cd flutter_acs_calling
bash tool/vendor_acs_sdk.sh   # bundles the ACS Web SDK — needed for BOTH Android and Web
cd example
flutter run

The example boots to a setup screen asking for a backend URL — the plugin talks to a small signaling backend you host (contract below).

Getting Started

Installation

This package is distributed from GitHub:

dependencies:
  flutter_acs_calling:
    git:
      url: https://github.com/guss1215/flutter_acs_calling.git
      ref: v0.1.1

Why not on pub.dev (yet)? The web platform relies on a bundled build of Microsoft's ACS Calling Web SDK, whose license does not allow redistributing the bundle. Consumers generate it locally with tool/vendor_acs_sdk.sh — a step the read-only pub cache cannot run. Publishing is on the roadmap once the bundle is supplied by the host app instead.

Android Configuration

Add the following permissions to your android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>

The bundled ACS Android SDK declares its own android:label, so your <application> element must override it or the manifest merge fails:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application
        ...
        tools:replace="android:label">

Make sure your minSdkVersion is at least 23:

android {
    defaultConfig {
        minSdk = 23
    }
}

Web Configuration

Load the ACS bridge script in your app's web/index.html. It is served from the plugin's bundled assets, so there is nothing to copy by hand:

<script src="assets/packages/flutter_acs_calling/assets/calling/acs_bridge.js" defer></script>

The bridge loads a vendored bundle of the ACS Calling Web SDK (assets/calling/acs_calling_sdk.js). That bundle is not committed — Microsoft distributes the SDK under its own license terms — so after cloning this repository, generate it locally (requires Node ≥ 18):

bash tool/vendor_acs_sdk.sh

CI runs the same script before building the example for web.

Usage

The integration contract has five steps (the example app shows all of them working together):

import 'package:flutter_acs_calling/flutter_acs_calling.dart';

// 1. Configure the backend URL before using any API (in main(), or once the
//    user has picked an environment). On web, mirror it to the JS bridge.
ApiService.setApiUrl('https://your-acs-backend.example.com');
setApiUrl('https://your-acs-backend.example.com'); // no-op outside web

// 2. If your backend endpoints require auth, sync the host app's JWT after
//    login:
await ApiService.saveAuthTokenToLocalStorage(token: jwt);

// 3. Mount the persistent video overlay ONCE, after an Overlay exists
//    (e.g. addPostFrameCallback in your shell screen), and dispose it on
//    logout:
VideoCallOverlayManager.instance.ensureMounted(context);

// 4. Stack IncomingCallOverlay over any screen that can receive calls —
//    it polls for calls and shows the popup with ringtone:
Stack(children: [myScreen, const IncomingCallOverlay()])

// 5. Start an outgoing call: signal the backend, get an ACS token, join.
final call = await AcsApi.initiateCall(targetUserId: id, callerName: myName);
final tokenRes = await AcsApi.getToken();
await PersistentWebViewState().joinCall(
  token: tokenRes.token,
  roomId: call.roomId,
  displayName: myName,
  callId: call.callId,
  userId: tokenRes.userId,
);

Required Backend API

This plugin requires a backend service that provides:

  • POST /api/token - Get ACS token for user
  • POST /api/token/refresh - Refresh ACS token
  • POST /api/rooms - Create a new room
  • POST /api/rooms/{roomId}/participants - Add participant to room
  • DELETE /api/rooms/{roomId}/participants - Remove participant from room
  • GET /api/rooms/{roomId} - Get room information
  • GET /api/calls/users - List callable users and their availability
  • POST /api/calls/initiate - Create a call (room + notify target user)
  • GET /api/calls/incoming - Poll incoming calls for the current user
  • GET /api/calls/{callId} - Get call details
  • POST /api/calls/{callId}/accept / .../reject / .../end - Call lifecycle
  • POST /api/calls/reset-my-presence - Clear an orphaned "busy" state

Every endpoint except token minting is trivial CRUD; tokens come from the Azure.Communication.Identity SDK (a few lines in any backend language). A minimal .NET sample backend is on the roadmap.

Architecture & tradeoffs

A few deliberate choices worth knowing before adopting (or interviewing me about):

  • One JS bridge for both platforms. The ACS Web Calling SDK runs inside a WebView on Android and an iframe-less bridge on web, so there is a single ACS integration to maintain. The native Android layer exists only where the WebView cannot go: MediaProjection screen capture (custom GL pipeline, foreground service, Android 14/15 compliance).
  • Vendored, pinned Web SDK. tool/vendor_acs_sdk.sh pins the SDK version and bundles it as a self-contained IIFE — the app keeps working on filtered/offline-ish networks and inside file:// WebViews, and Microsoft's license is respected by not committing the bundle.
  • Polling for incoming calls (3s), not push. It keeps the backend contract dependency-free (no SignalR/FCM infrastructure required to try the plugin). The production path is push signaling — see the roadmap.
  • Mid-call token refresh is scheduled from the JWT exp claim; long calls used to die silently at ~1 hour without it.

Known limits: no iOS yet, polling latency for incoming calls, and the backend is bring-your-own until the sample ships.

API Reference

VideoCallOverlayManager

Owns the single persistent video overlay (the call UI survives navigation).

VideoCallOverlayManager.instance.ensureMounted(context); // once, in the shell
VideoCallOverlayManager.instance.dispose();              // on logout

// Optional: brand the in-call header (defaults to 'Video call')
VideoCallOverlayManager.instance.ensureMounted(context, headerTitle: 'TeleHealth');

IncomingCallOverlay

Widget that polls for incoming calls and shows the popup (ringtone, accept/decline). Stack it over screens for roles that receive calls.

AcsApi

Typed client for the backend endpoints listed above (getToken, initiateCall, checkIncomingCalls, acceptCall, rejectCall, endCall, createRoom, addSelfToRoom, ...).

PersistentWebViewState

Drives the actual call: joinCall(...) requests camera/microphone permissions, shows the overlay and joins the ACS room.

CallHistoryStore

Device-local call log (ChangeNotifier): entries newest-first and a missedToday counter for badges.

Example

See the example directory for a complete sample application.

To run the example:

bash tool/vendor_acs_sdk.sh   # once, from the repo root
cd example
flutter run

Roadmap

  • iOS support — native ACS iOS SDK binding, or extending the WebView bridge
  • Push-based incoming-call signaling (SignalR / FCM) to replace polling
  • Minimal .NET sample backend (token + rooms + call signaling) so the example runs end-to-end in minutes
  • pub.dev publication once the ACS Web SDK bundle is host-app-supplied
  • CallKit / ConnectionService integration for OS-level call UX

Dependencies

  • flutter_inappwebview: ^6.1.5 - Web view for web platform
  • permission_handler: ^12.0.1 - Permission handling
  • http: ^1.5.0 - HTTP requests
  • shared_preferences: ^2.5.3 - Local storage
  • Azure Communication Services SDK for Android

License

MIT — see LICENSE.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About the author

Built by Gustavo Guillen — Flutter & .NET developer focused on offline-first health tech and real-time video (Azure Communication Services). This plugin was extracted from a production telehealth platform I work on.

Available for freelance work (Flutter · .NET · Azure). GitHub · LinkedIn · guss1215@gmail.com

For issues, feature requests, or questions, please file an issue on this repository.

About

Flutter plugin for Azure Communication Services video calling — WebView/JS bridge, native Android screen share (Android 15-ready), adaptive video quality

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages