# Use Nosmai Effects with Agora in Flutter

> Publish the processed Flutter camera feed through the official Nosmai Agora bridge on iOS and Android.

> For AI agents: the complete documentation index is at https://nosmai.com/llms/effects.txt

Product: Nosmai Effects
Group: integrations
Source: https://nosmai.com/docs/effects/integrations/agora/flutter/

## The streaming path

![Camera frames pass through Nosmai Effects before Agora publishes them](/images/docs/agora-streaming-flow-v1.svg)

Nosmai owns and processes the camera feed. Agora publishes only the processed
output while the application keeps its local preview and session controls.

## Prerequisites

- A working [Flutter quickstart](/docs/effects/quickstart/flutter/)
- An Agora project, token flow, and broadcaster permissions

## Install the packages

```yaml
dependencies:
  nosmai_effects_sdk: ^1.0.2
  nosmai_agora_bridge:
    git:
      url: https://github.com/nosmai/nosmai_agora_bridge.git
  agora_rtc_engine: ^6.5.3
```

Declare and request camera and microphone permissions on both platforms.

## Use the required initialization order

Create the shared native engine before Nosmai creates its graphics context:

```dart
final handle = await NosmaiAgoraBridge.getNativeHandle(
  agoraAppId: agoraAppId,
);

final initialized = await NosmaiFlutter.initialize(nosmaiLicenseKey);
if (!initialized) {
  throw StateError('Nosmai initialization was not accepted');
}

final engine = createAgoraRtcEngine(sharedNativeHandle: handle);
```

Calling `NosmaiFlutter.initialize` first can leave Android remote video black.
On iOS, using the bridge handle also prevents two Dart wrappers from driving
conflicting native engine state.

## Configure a portrait stream

```dart
await engine.initialize(RtcEngineContext(appId: agoraAppId));
await engine.enableVideo();
await engine.enableAudio();

await engine.setVideoEncoderConfiguration(
  const VideoEncoderConfiguration(
    dimensions: VideoDimensions(width: 720, height: 1280),
    frameRate: 30,
    orientationMode: OrientationMode.orientationModeFixedPortrait,
  ),
);
```

## Start streaming

```dart
final started = await NosmaiAgoraBridge.startStreaming(
  channelName: channelName,
  token: token?.isEmpty == true ? null : token,
  uid: uid,
);
```

The bridge joins with a custom video track. Do not also join or publish the raw
camera on this path. Guard the button against duplicate starts.

## Stop and dispose

```dart
await NosmaiAgoraBridge.stopStreaming();
if (!Platform.isIOS) {
  await engine.leaveChannel();
}

await engine.release();
await NosmaiAgoraBridge.disposeNative();
```

Call `stopStreaming` for each session. Release the engine and bridge only when
the whole live experience is being torn down.

## Common errors

| Symptom | Cause | Fix |
| --- | --- | --- |
| Local preview works but remote video is black | The shared native handle was created too late or ignored | Call `getNativeHandle` before Nosmai initialization and wrap the same handle |
| Second live session freezes | The first bridge session was not stopped | Pair every successful start with `stopStreaming` |
| Remote video is cropped | Encoder aspect or orientation differs from 720 by 1280 portrait output | Use fixed portrait and do not override it later |
| iOS camera switch loses output | The bridge was not notified | Call `NosmaiAgoraBridge.notifyCameraSwitch()` after switching |

## Verify it worked

Apply a visible effect, join from a second device, and confirm the remote video
is filtered, portrait-oriented, and stable after stop and rejoin.

## Next steps

- [Compare live-streaming routes](/docs/effects/integrations/live-streaming/)
- [Understand off-screen rendering](/docs/effects/concepts/off-screen-rendering/)
- [Review the Flutter platform guide](/docs/effects/platforms/flutter/)

## References

- [Nosmai Agora bridge](https://github.com/nosmai/nosmai_agora_bridge)
- [Flutter platform guide](/docs/effects/platforms/flutter/)
- [Off-screen rendering](/docs/effects/concepts/off-screen-rendering/)
