mebius 0.2.0 copy "mebius: ^0.2.0" to clipboard
mebius: ^0.2.0 copied to clipboard

Mebius live video SDK for Flutter — broadcast and watch real-time streams through the Mebius gateway with a single, simple API.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:mebius/mebius.dart';

/// Replace these with values for your Mebius account and credentials minted by
/// your backend. The client never holds your secret key.
const String kAppId = 'your-app-id';
const String kGateway = 'https://gateway.mebius.example';
const String kToken = 'paste-a-short-lived-token-from-your-backend';

/// The `deliveries` list your backend received alongside the token.
///
/// Paste it through untouched — `kind` is a Mebius intent label, not a media
/// format, and `path` is resolved by Mebius. Leaving it empty still plays, but
/// every viewer is served from Mebius origin instead of the nearest edge, and on
/// mobile that is billed per viewer.
///
/// Example of the shape your backend returns:
///   [ {"kind": "wide", "path": "/d/wide/room-42"},
///     {"kind": "local", "path": "/live/room-42/index.m3u8"} ]
const List<MebiusDelivery> kDeliveries = <MebiusDelivery>[];

void main() {
  // Configure the SDK once, before the app starts.
  Mebius.init(appId: kAppId, gateway: kGateway);
  runApp(const MebiusExampleApp());
}

/// Root widget for the example app.
class MebiusExampleApp extends StatelessWidget {
  /// Creates the example app.
  const MebiusExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Mebius Example',
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      home: const HomeScreen(),
    );
  }
}

/// Landing screen offering the broadcast and watch flows.
class HomeScreen extends StatefulWidget {
  /// Creates the home screen.
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  MebiusClient? _client;

  MebiusClient _ensureClient() {
    return _client ??= Mebius.connect(token: kToken, deliveries: kDeliveries);
  }

  @override
  void dispose() {
    _client?.disconnect();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Mebius Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            FilledButton.icon(
              icon: const Icon(Icons.videocam),
              label: const Text('Broadcast'),
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute<void>(
                  builder: (_) => BroadcastScreen(client: _ensureClient()),
                ),
              ),
            ),
            const SizedBox(height: 16),
            FilledButton.icon(
              icon: const Icon(Icons.play_circle),
              label: const Text('Watch'),
              onPressed: () => Navigator.of(context).push(
                MaterialPageRoute<void>(
                  builder: (_) => WatchScreen(client: _ensureClient()),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// Screen that captures and broadcasts the local camera and microphone.
class BroadcastScreen extends StatefulWidget {
  /// Creates the broadcast screen bound to [client].
  const BroadcastScreen({required this.client, super.key});

  /// The connected Mebius client.
  final MebiusClient client;

  @override
  State<BroadcastScreen> createState() => _BroadcastScreenState();
}

class _BroadcastScreenState extends State<BroadcastScreen> {
  final TextEditingController _streamId =
      TextEditingController(text: 'demo-stream');
  late final MebiusBroadcaster _broadcaster =
      widget.client.createBroadcaster();
  bool _live = false;
  bool _micOn = true;
  bool _camOn = true;
  String _status = 'Idle';

  @override
  void initState() {
    super.initState();
    _broadcaster.events.listen((event) {
      if (!mounted) {
        return;
      }
      setState(() {
        switch (event.type) {
          case MebiusBroadcasterEventType.started:
            _status = 'Live';
            _live = true;
          case MebiusBroadcasterEventType.stopped:
            _status = 'Stopped';
            _live = false;
          case MebiusBroadcasterEventType.stats:
            _status = 'Live • ${event.stats?.frameRate.toStringAsFixed(0)} fps';
        }
      });
    });
  }

  Future<void> _toggleBroadcast() async {
    try {
      if (_live) {
        await _broadcaster.stop();
      } else {
        await _broadcaster.start(_streamId.text.trim());
      }
    } on MebiusError catch (e) {
      _showError(e);
    }
  }

  void _showError(MebiusError e) {
    if (!mounted) {
      return;
    }
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('${e.codeName}: ${e.message}')),
    );
  }

  @override
  void dispose() {
    _broadcaster.dispose();
    _streamId.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Broadcast — $_status')),
      body: Column(
        children: [
          Expanded(child: MebiusView(broadcaster: _broadcaster)),
          Padding(
            padding: const EdgeInsets.all(12),
            child: TextField(
              controller: _streamId,
              decoration: const InputDecoration(
                labelText: 'Stream ID',
                border: OutlineInputBorder(),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                IconButton.filledTonal(
                  icon: Icon(_micOn ? Icons.mic : Icons.mic_off),
                  onPressed: () {
                    setState(() => _micOn = !_micOn);
                    _broadcaster.setMicEnabled(enabled: _micOn);
                  },
                ),
                IconButton.filledTonal(
                  icon: Icon(_camOn ? Icons.videocam : Icons.videocam_off),
                  onPressed: () {
                    setState(() => _camOn = !_camOn);
                    _broadcaster.setCameraEnabled(enabled: _camOn);
                  },
                ),
                IconButton.filledTonal(
                  icon: const Icon(Icons.cameraswitch),
                  onPressed: _broadcaster.switchCamera,
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: SizedBox(
              width: double.infinity,
              child: FilledButton(
                onPressed: _toggleBroadcast,
                child: Text(_live ? 'Stop' : 'Start'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Screen that plays a stream, toggling between latency-optimized and
/// scale-optimized modes.
class WatchScreen extends StatefulWidget {
  /// Creates the watch screen bound to [client].
  const WatchScreen({required this.client, super.key});

  /// The connected Mebius client.
  final MebiusClient client;

  @override
  State<WatchScreen> createState() => _WatchScreenState();
}

class _WatchScreenState extends State<WatchScreen> {
  final TextEditingController _streamId =
      TextEditingController(text: 'demo-stream');
  MebiusPlayerMode _mode = MebiusPlayerMode.auto;
  MebiusPlayer? _player;
  bool _playing = false;
  double _volume = 1;
  String _status = 'Idle';

  Future<void> _play() async {
    await _stop();
    final player = widget.client.createPlayer(mode: _mode)
      ..events.listen((event) {
        if (!mounted) {
          return;
        }
        setState(() {
          switch (event.type) {
            case MebiusPlayerEventType.playing:
              _status = 'Playing';
              _playing = true;
            case MebiusPlayerEventType.buffering:
              _status = 'Buffering…';
            case MebiusPlayerEventType.ended:
              _status = 'Ended';
              _playing = false;
            case MebiusPlayerEventType.stats:
              _status = 'Playing • '
                  '${event.stats?.frameRate.toStringAsFixed(0)} fps';
          }
        });
      });
    _player = player;
    setState(() {});
    try {
      await player.play(_streamId.text.trim());
      await player.setVolume(_volume);
    } on MebiusError catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('${e.codeName}: ${e.message}')),
        );
      }
    }
  }

  Future<void> _stop() async {
    final player = _player;
    _player = null;
    if (player != null) {
      await player.dispose();
    }
    if (mounted) {
      setState(() => _playing = false);
    }
  }

  @override
  void dispose() {
    _player?.dispose();
    _streamId.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final player = _player;
    return Scaffold(
      appBar: AppBar(title: Text('Watch — $_status')),
      body: Column(
        children: [
          Expanded(
            child: player == null
                ? const ColoredBox(color: Colors.black)
                : MebiusView.player(player: player),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: TextField(
              controller: _streamId,
              decoration: const InputDecoration(
                labelText: 'Stream ID',
                border: OutlineInputBorder(),
              ),
            ),
          ),
          SegmentedButton<MebiusPlayerMode>(
            segments: const [
              ButtonSegment(
                value: MebiusPlayerMode.auto,
                label: Text('Auto'),
              ),
              ButtonSegment(
                value: MebiusPlayerMode.lowLatency,
                label: Text('Low latency'),
              ),
              ButtonSegment(
                value: MebiusPlayerMode.scale,
                label: Text('Scale'),
              ),
            ],
            selected: {_mode},
            onSelectionChanged: (selection) async {
              setState(() => _mode = selection.first);
              if (_playing) {
                await _play();
              }
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: Row(
              children: [
                const Icon(Icons.volume_up),
                Expanded(
                  child: Slider(
                    value: _volume,
                    onChanged: (v) {
                      setState(() => _volume = v);
                      _player?.setVolume(v);
                    },
                  ),
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: SizedBox(
              width: double.infinity,
              child: FilledButton(
                onPressed: _playing ? _stop : _play,
                child: Text(_playing ? 'Stop' : 'Play'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
0
points
75
downloads

Publisher

unverified uploader

Weekly Downloads

Mebius live video SDK for Flutter — broadcast and watch real-time streams through the Mebius gateway with a single, simple API.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_webrtc, http, meta, video_player

More

Packages that depend on mebius