aws_ivs_realtime
Flutter plugin for Amazon IVS Real-Time: low-latency multi-participant live video (stage / “broadcast” grid) on Android and iOS, plus optional Amazon IVS Chat for room-style text messages alongside the stream.
What you get:
- Real-time live streaming (Stages) — native participant grid (host and viewers), camera/mic publish, mute, leave; bridged with a MethodChannel and platform view (
IvsRealtimePlatform,AwsIvsRealtimePlatformView.viewType). - Control plane in Dart (optional) — create/list/delete stages, mint participant tokens, create/delete chat rooms, mint chat tokens — either by implementing one backend-facing interface (
IvsLiveControlPlane) or by using the built-in SigV4-on-device implementation (IvsAwsSigV4ControlPlane) for prototypes only. - IVS Chat WebSocket —
IvsChatSessionafter you obtain a chat token. - Runtime permissions —
permission_handlerbefore join/publish.
Live streaming only vs live + chat
- Streaming only (no chat): use
IvsRealtimePlatform+ the platform view (AwsIvsRealtimePlatformView.viewType) and a participant token frommintParticipantToken/ your backend. Do not createIvsChatSession, do not callmintChatToken/createChatRoom, and do not add chat UI widgets. The native stage never shows a chat pane by itself—there is no plugin-wide “chat on/off” flag; chat appears only if your app wires it up. - Streaming + chat: additionally connect
IvsChatSessionand build your own message list / composer. IvsLiveControlPlanelists chat APIs alongside Real-Time APIs so one implementation can power both products; you may still call only the stage-related methods from your UI—unused chat methods stay unused (no extra change inside this package).- The
example/app demonstrates both together for convenience; a minimal integration can be video only.
Source repository, pub.dev, and where to run the demo
| What | URL / path |
|---|---|
| GitHub (browse source) | https://github.com/vipulbansal/aws_ivs_realtime |
| Clone | git clone https://github.com/vipulbansal/aws_ivs_realtime.git |
| Issues | https://github.com/vipulbansal/aws_ivs_realtime/issues |
| Runnable example app | Folder example/ in that repository — open example/ on GitHub |
| Published package | https://pub.dev/packages/aws_ivs_realtime |
| Sample app (app-only, no backend) | aws_ivs_realtime_usage — standalone Flutter repo: use the package from the app only (no separate backend) as a reference for integration |
Reference — app-only sample: vipulbansal/aws_ivs_realtime_usage is a standalone Flutter project (not the in-repo
example/) that shows how to wireaws_ivs_realtimewithout a backend—ideal if you depend on pub.dev and want a full app tree to copy from. Clone: https://github.com/vipulbansal/aws_ivs_realtime_usage.
Two layers (do not confuse them)
| Layer | Purpose | Key type |
|---|---|---|
| Native stage | Render tiles + send/receive real-time A/V | IvsRealtimePlatform.join needs only the IVS participant token (opaque string from AWS CreateParticipantToken). Never pass IAM access key / secret here. |
| Control plane (Dart) | Create stages, list stages, mint participant/chat tokens, delete resources | Either your backend (implement IvsLiveControlPlane) or IAM credentials inside the app via IvsAwsSigV4ControlPlane (non-production pattern). |
Backend integration: implement IvsLiveControlPlane
If your AWS keys and SigV4 signing live only on your servers, the Flutter side should implements IvsLiveControlPlane.
What you must do (no ambiguity)
- Create a Dart class that
implements IvsLiveControlPlane(seelib/ivs_live_control_plane.dartfor exact signatures). - Implement every method in that abstract class. Each method corresponds one-to-one to an AWS operation listed below. Your implementation typically:
- forwards the method arguments to your HTTP/gRPC/mobile-BFF API using your URLs and auth; then
- parses your API’s JSON into the Dart return type shown.
- Your server performs the real AWS call (with an IAM role or temporary credentials). The app never receives long-lived IAM user keys in this model.
- Reference:
IvsAwsSigV4ControlPlanein this repo is the same interface already implemented using SigV4 from the device — use its source as a behavioral reference for what each operation is supposed to accomplish (then replace networking with calls to your backend).
This README does not define your REST paths or JSON field names; only the Dart contract is fixed.
Methods you must implement (full contract)
| You implement | AWS operation (your backend must honor this semantics) | Dart return type |
|---|---|---|
mintParticipantToken |
CreateParticipantToken | Future<String> — only the participant token string (same value as participantToken.token in the AWS JSON). |
listStages |
ListStages | Future<List<Map<String, dynamic>>> — one map per stage summary (same information you would map from AWS stageList items). |
createStage |
CreateStage | Future<Map<String, dynamic>> — stage object / metadata as maps (equivalent to parsing AWS stage + related fields your UI needs). |
deleteStage |
DeleteStage | Future<void> |
createChatRoom |
CreateRoom (IVS Chat) | Future<Map<String, dynamic>> — room resource (same semantics as AWS room in the response). |
deleteChatRoom |
DeleteRoom | Future<void> |
mintChatToken |
CreateChatToken | Future<Map<String, dynamic>> — token payload your app passes into IvsChatSession (same logical fields AWS returns for a chat token). |
Typical live flow with a backend: createStage (once) → share stage ARN → for each participant mintParticipantToken → IvsRealtimePlatform.join(token: ...) with native grid widget → optional: createChatRoom / mintChatToken / IvsChatSession for messages.
After you implement it: how to use IvsLiveControlPlane in your app
Implementing the interface is only half the work. You also instantiate your class and call its methods from your widgets / controllers, then feed the return values into the other types from this package.
| Object | Type | Role |
|---|---|---|
control |
Your implements IvsLiveControlPlane |
Talks to your backend (or wraps it). Produces tokens and stage/chat metadata. |
stage |
IvsRealtimePlatform |
Talks to native Android/iOS IVS Real-Time (join / leave / mute / events). Does not use your interface by itself — you call both. |
| Grid widget | AndroidView / UiKitView |
Renders video tiles; see the Platform view subsection (under App-only below). |
1) Live video (Stages) — what goes where
- Build
final control = MyBackendControlPlane(/* your HTTP client, base URL, auth */);andfinal stage = IvsRealtimePlatform();(oftenStatefields). - Discover or create a stage (optional):
await control.listStages(region: region)for UI lists, orfinal created = await control.createStage(region: region, name: name). Your implementation’s map should include something you can treat as stage ARN (same information as AWS CreateStage / ListStages — commonly anarnfield on the stage object). - Mint a participant token for the current user:
final participantToken = await control.mintParticipantToken(region: region, stageArn: stageArn, userId: optionalUserId, durationMinutes: 120, capabilities: const ['PUBLISH', 'SUBSCRIBE']);
The returnedStringis the only value that ever goes to native join. - Show the grid in the widget tree (platform view, non-zero size) — same snippet as in the Platform view subsection under App-only below.
- Attach to the stage:
await stage.join(token: participantToken, publish: isHostOrPublisher);
Mic permission is always requested; camera whenpublishis true. - While live: use
stage.setPublish,stage.setLocalStreamMuted, listen tostage.stageConnectionEventsfor disconnect / host-ended flows. - When done:
await stage.leave();and, if you no longer need the AWS stage,await control.deleteStage(region: region, stageArn: stageArn).
Your backend’s JSON shapes are yours; the Dart side only needs to extract String / Map / List values that match the meanings in the table above so you can pass them into join, UI models, and chat below.
2) IVS Chat (optional) — chaining IvsLiveControlPlane → IvsChatSession
- Room:
final room = await control.createChatRoom(region: region, name: roomDisplayName);— takeroomArn(or equivalent) from the map your backend returns (AWS CreateRoom exposes the room ARN). - Session:
final chat = IvsChatSession(); - Connect:
await chat.connect(region: region, resolveChatToken: () async { final m = await control.mintChatToken(region: region, roomArn: roomArn, userId: stableUserId, attributes: {...}); final t = m['token'] as String?; if (t == null || t.isEmpty) throw StateError('no chat token'); return t; });
IvsChatSession.connectcallsresolveChatTokenagain on reconnect, so each call should hit your backend for a fresh token (short-lived), not reuse one cached string forever. - UI: listen to
chat.lines(Stream<IvsChatLine>) for incoming messages; usechat.sendMessagewhen the socket is open. - Teardown:
await chat.dispose();and optionallyawait control.deleteChatRoom(region: region, roomArn: roomArn).
3) Summary
IvsLiveControlPlane= how your Flutter code gets AWS-shaped data from your servers.IvsRealtimePlatform+ platform view = how that data becomes live video on device.IvsChatSession= how chat tokens from the same interface become a WebSocket message stream.
App-only (no backend): where IAM keys and ARNs go
Preferred built-in type: IvsAwsSigV4ControlPlane with an IvsAwsCredentialResolver — a zero-argument function that returns (accessKeyId, secretAccessKey, sessionToken?).
Put credentials only in one of these places (pick one strategy and stick to it):
| Strategy | Where you put access key ID, secret key, optional session token | Where you put AWS region and stage ARN |
|---|---|---|
| A. Build-time defines | Inside the resolver, read const String.fromEnvironment('AWS_ACCESS_KEY_ID'), 'AWS_SECRET_ACCESS_KEY', optional 'AWS_SESSION_TOKEN' |
Same pattern: String.fromEnvironment('AWS_REGION', defaultValue: 'us-east-1'), String.fromEnvironment('IVS_STAGE_ARN') |
| B. Runtime UI or secure storage | Resolver reads TextEditingController.text, flutter_secure_storage, env via Platform.environment, etc. |
Passed as variables into mintParticipantToken(region: ..., stageArn: ...) from your own state (text fields, remote config, etc.) |
| C. Literals in source | Do not commit keys in Dart string literals to a public repository. | Same |
Run / build with compile-time defines (strategy A), from your app directory:
flutter run \
--dart-define=AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID \
--dart-define=AWS_SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY \
--dart-define=AWS_REGION=us-east-1 \
--dart-define=IVS_STAGE_ARN=arn:aws:ivs:us-east-1:123456789012:stage/yourStageId
Optional: who this participant is on the stage (otherwise AWS / tiles only see an anonymous participant):
--dart-define=IVS_PARTICIPANT_USER_ID=alice-12345
Optional temporary credentials:
--dart-define=AWS_SESSION_TOKEN=YOUR_SESSION_TOKEN
Minimal usage after keys are available:
import 'package:aws_ivs_realtime/aws_ivs_realtime.dart';
final ivs = IvsRealtimePlatform();
final control = IvsAwsSigV4ControlPlane(
resolveCredentials: () => (
accessKeyId: const String.fromEnvironment('AWS_ACCESS_KEY_ID'),
secretAccessKey: const String.fromEnvironment('AWS_SECRET_ACCESS_KEY'),
sessionToken: _opt('AWS_SESSION_TOKEN'),
),
);
String? _opt(String k) {
const v = String.fromEnvironment(k, defaultValue: '');
return v.isEmpty ? null : v;
}
Future<void> joinWithSigV4() async {
const region = String.fromEnvironment('AWS_REGION', defaultValue: 'us-east-1');
const stageArn = String.fromEnvironment('IVS_STAGE_ARN');
const participantUserId = String.fromEnvironment('IVS_PARTICIPANT_USER_ID', defaultValue: '');
final token = await control.mintParticipantToken(
region: region,
stageArn: stageArn,
// Sent to AWS CreateParticipantToken as `userId` — use your real signed-in id / handle.
userId: participantUserId.isEmpty ? null : participantUserId,
);
await ivs.join(token: token, publish: true);
}
Where the “name” comes from (app-only is the same idea as backend):
- Stage / live video: the README snippet above did not show a name before because
userIdis optional. PassuserId:intomintParticipantToken(from auth, profile, or--dart-define=IVS_PARTICIPANT_USER_IDas a stand-in). That value is what AWS associates with the participant for the Real-Time session; the native grid labels participants using IVS metadata—not a hardcoded “Flutter demo user” from this package. - IVS Chat sender labels: set when you call
control.mintChatToken(..., userId: ..., attributes: {'displayName': 'Alice Smith'}). Your UI readsIvsChatLineevents; other clients see the attributes / user id your app sent. There is no separate “name” field onIvsRealtimePlatform.join—only the opaque participant token—so identity for chat is always viamintChatToken(or your backend’s equivalent).
Important: mintParticipantToken + join do not draw anything on screen by themselves. You must embed the native participant grid with a Flutter platform view or you will hear/encode media but see no tiles / video.
Platform view: show the live grid (AndroidView / UiKitView)
Use the constant AwsIvsRealtimePlatformView.viewType (ivs_stage_view). Match the plugin’s codec registration with StandardMessageCodec(). Give the view a non-zero size (Expanded, SizedBox.expand, etc.). Backend and app-only flows both use this widget — only the way you obtain the participant token differs.
import 'dart:io';
import 'package:aws_ivs_realtime/aws_ivs_realtime.dart';
import 'package:flutter/cupertino.dart' show UiKitView;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget ivsParticipantGrid() {
if (!ivsNativeStageSupported) {
return const Center(child: Text('Stages are only on Android and iOS.'));
}
if (Platform.isAndroid) {
return AndroidView(
viewType: AwsIvsRealtimePlatformView.viewType,
layoutDirection: TextDirection.ltr,
creationParamsCodec: StandardMessageCodec(),
);
}
return UiKitView(
viewType: AwsIvsRealtimePlatformView.viewType,
layoutDirection: TextDirection.ltr,
creationParamsCodec: StandardMessageCodec(),
);
}
// Typical layout: put the platform view in an Expanded, then call joinWithSigV4()
// (or join with a backend-minted token). If tiles stay blank after join, call
// ivs.refreshStageBindings() once from a post-frame callback — see DOCUMENTATION.md.
Listing stages, showing them in Flutter UI, and joining an existing stage
The same IvsAwsSigV4ControlPlane (or your IvsLiveControlPlane backend implementation) exposes listStages, createStage, deleteStage, chat helpers, etc. Typical Dart-only flow:
await control.listStages(region: region)→List<Map<String, dynamic>>(each map is an AWS stage summary; at minimum expect anarnand often aname).- Render that list with normal Flutter widgets (
ListView,ListTile,DataTable, …) — this is where you choose titles, subtitles, pull-to-refresh, empty states, etc. - When the user picks a row, read
stage['arn'] as Stringand callawait control.mintParticipantToken(region: region, stageArn: arn, userId: …), thenawait ivs.join(token: token, publish: …)with the platform view already in the tree.
Optional: await control.createStage(region: region, name: 'my-show-001', tags: {...}) returns the created stage Map (includes arn); mint a token for that ARN to go live as host.
import 'package:aws_ivs_realtime/aws_ivs_realtime.dart';
Future<void> refreshStageCatalog({
required IvsAwsSigV4ControlPlane control,
required String region,
required void Function(List<Map<String, dynamic>> stages) onStages,
}) async {
final stages = await control.listStages(region: region);
onStages(stages);
}
Future<void> joinExistingStageFromRow({
required IvsAwsSigV4ControlPlane control,
required IvsRealtimePlatform ivs,
required String region,
required String stageArn,
String? participantUserId,
bool publish = false,
}) async {
final token = await control.mintParticipantToken(
region: region,
stageArn: stageArn,
userId: participantUserId,
);
await ivs.join(token: token, publish: publish);
}
// Example list wiring (simplified — use setState / provider / bloc in a real app):
Widget stagePicker({
required List<Map<String, dynamic>> stages,
required void Function(String arn) onPickArn,
}) {
return ListView.builder(
itemCount: stages.length,
itemBuilder: (context, i) {
final s = stages[i];
final arn = s['arn'] as String?;
final title = s['name'] as String? ?? arn ?? 'Stage';
return ListTile(
title: Text(title),
subtitle: arn != null
? Text(arn, maxLines: 1, overflow: TextOverflow.ellipsis)
: null,
onTap: arn == null ? null : () => onPickArn(arn),
);
},
);
}
If you use tags to mark which stages are “live” (as the repo example/ does with IvsRealtimeStagesApi.tagStatus), filter in Dart with stages.where(...) before building the list — the API returns all stages the credentials can see, not only live ones.
Native stage: embed the grid and join (after you have a participant token)
This is the same for backend or app-only paths: once you have a participant token string, the native video UI always goes through the platform view above plus IvsRealtimePlatform.join.
- Host app: Android
minSdk = 28on your app module; iOS 14+; permissions andpermission_handleriOS preprocessor flags — see Requirements and Install. - Widget:
AndroidView(Android) orUiKitView(iOS) — same Platform view snippet as in the App-only section above (required for any participant token source). - Join:
await ivs.join(token: participantTokenString, publish: hostPublishesA/V); - Events / lifecycle: subscribe to
stageConnectionEvents; callleavewhen done; optionallyrefreshStageBindingsafter first frame if tiles misbind — details in DOCUMENTATION.md.
AWS keys vs participant token
| String | Pass to join(token:)? |
Role |
|---|---|---|
| Participant token | Yes | Output of CreateParticipantToken; consumed by native IVS SDK. |
| Access key ID / secret / session token | No | Only for signing AWS HTTPS in Dart (IvsAwsSigV4ControlPlane) or on your server. |
What you need to know first
| Step | Responsibility |
|---|---|
| 1. Stage exists | You (console, IaC, createStage via control plane, etc.). |
| 2. Participant token | mintParticipantToken on your IvsLiveControlPlane implementation or IvsAwsSigV4ControlPlane / IvsRealtimeTokenClient. |
| 3. Native UI + join | AwsIvsRealtimePlatformView.viewType + IvsRealtimePlatform.join. |
Where everything lives in the package
| Goal | API |
|---|---|
| Native join / leave / mute / publish | IvsRealtimePlatform |
Native grid viewType |
AwsIvsRealtimePlatformView |
| Backend-shaped control plane (you implement) | IvsLiveControlPlane |
| SigV4 on device (implements same interface) | IvsAwsSigV4ControlPlane |
| Low-level participant token HTTP + SigV4 | IvsRealtimeTokenClient |
| Stage REST helpers (keys passed per call) | IvsRealtimeStagesApi |
| Chat REST helpers | IvsChatApi |
| Chat WebSocket | IvsChatSession |
Requirements
| Platform | Minimum | Notes |
|---|---|---|
| Android | API 28 | RECORD_AUDIO, CAMERA when publishing |
| iOS | 14.0 | NSMicrophoneUsageDescription, NSCameraUsageDescription |
Native SDKs: Android Maven ivs-broadcast stages AAR (1.41.0 in this repo); iOS CocoaPods AmazonIVSBroadcast/Stages (~> 1.36.0 in the podspec).
Install
From pub.dev:
dependencies:
aws_ivs_realtime: ^0.1.4
From Git:
dependencies:
aws_ivs_realtime:
git:
url: https://github.com/vipulbansal/aws_ivs_realtime.git
Android — your AndroidManifest.xml (required)
The plugin’s own android/src/main/AndroidManifest.xml does not declare network or A/V permissions. Your application module must include them so the merged app manifest allows IVS Real-Time and permission_handler prompts.
Inside android/app/src/main/AndroidManifest.xml, as direct children of <manifest> (before <application>), add at least:
<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.MODIFY_AUDIO_SETTINGS" />
INTERNET— AWS HTTPS, IVS signaling, IVS Chat WebSocket.RECORD_AUDIO— join stage (subscribe/publish audio).CAMERA— publish video whenjoin(..., publish: true)/ host camera.MODIFY_AUDIO_SETTINGS— used in the reference example for audio routing; safe to keep for parity withexample/android/app/src/main/AndroidManifest.xml.
Declaring these is not optional: the Dart side only requests runtime access via permission_handler; the OS still requires the <uses-permission> entries in your manifest.
iOS — your Info.plist + Podfile (required)
Add (or merge) usage descriptions in ios/Runner/Info.plist — replace the strings with copy appropriate for your app:
<key>NSCameraUsageDescription</key>
<string>Camera is used to publish your video to the IVS Real-Time stage when you go live.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone is used to publish and receive audio on the IVS Real-Time stage.</string>
In ios/Podfile, use at least iOS 14 and, if CocoaPods requires it, use_modular_headers! (see example/ios/Podfile).
permission_handler on iOS: copy the GCC_PREPROCESSOR_DEFINITIONS block from example/ios/Podfile post_install (PERMISSION_MICROPHONE=1, PERMISSION_CAMERA=1) so microphone/camera code is compiled into permission_handler_apple; otherwise dialogs never appear.
Customizing buttons and layout around the live stream (Flutter / Dart)
The native participant grid (video tiles inside AndroidView / UiKitView) is drawn by the plugin’s embedded Android / iOS UI. You do not need to edit native XML or Swift to:
- Add lobbies, toolbars, bottom sheets, navigation, theme colors, typography, padding, or any Flutter layout around the platform view (
Stack,Scaffold,SafeArea,Row/Column, etc.). - Build lists of stages, Join / Leave buttons, host vs viewer toggles, or overlays (mute, end stream) — all in Dart, calling
IvsLiveControlPlane/IvsAwsSigV4ControlPlane+IvsRealtimePlatformas in the Listing stages… snippet under App-only earlier in this README.
Treat the platform view like a single embedded surface: you control everything outside and around it with normal Flutter widgets. Changing how each internal video tile looks (pixel-level native skin) is outside typical app integration and is not required for custom product UI.
By default, each native tile is video only (no subscribe/mute/dB strip). Call IvsRealtimePlatform.setShowParticipantStateOverlay with true before join if you want the previous demo-style labels on the tiles.
Example app (run the demo from GitHub)
The authoritative runnable project lives in the example/ directory of https://github.com/vipulbansal/aws_ivs_realtime (example/ tree). Use it to see lobby → full-screen live → optional IVS Chat, plus a switch between SigV4 on device and a stub IvsLiveControlPlane implementation.
App-only reference (no backend): for a separate Flutter app that consumes the published package and does not use a backend, see aws_ivs_realtime_usage on GitHub — https://github.com/vipulbansal/aws_ivs_realtime_usage. Listed as Sample app (app-only, no backend) in the table at the top of this README.
Run locally (after you clone https://github.com/vipulbansal/aws_ivs_realtime.git):
cd aws_ivs_realtime/example
flutter pub get
flutter run
Use a physical device or emulator that meets Requirements. For SigV4 from defines, pass the same --dart-define=... values documented under App-only in this README.
If you depend on the package from pub.dev only, your own app will not contain this example/ tree — clone the GitHub repository whenever you want to run or copy from the demo.
Documentation
Channels, SigV4 service scope (ivs vs ivsrealtime), troubleshooting: DOCUMENTATION.md.
Contributing
Issues and pull requests: https://github.com/vipulbansal/aws_ivs_realtime (same repository linked in Source repository at the top of this README).
Publishing (maintainers)
dart pub publish from package root. Do not ship IAM credentials in app binaries or public repos.
License
See LICENSE.
Libraries
- aws_ivs_realtime
- Amazon IVS Real-Time (Stages) for Flutter (Android + iOS), plus optional Dart helpers for the control plane (participant tokens, stages) and IVS Chat.
- ivs_chat_api
- ivs_chat_session
- ivs_live_control_plane
- ivs_realtime_platform
- ivs_realtime_stages_api
- ivs_realtime_token_client