rtmp_streaming 1.0.0 copy "rtmp_streaming: ^1.0.0" to clipboard
rtmp_streaming: ^1.0.0 copied to clipboard

A Flutter plugin for Camera and Microphone streaming library via RTMP.

example/lib/main.dart

// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rtmp_streaming/camera.dart';
import 'package:wakelock_plus/wakelock_plus.dart';

class CameraExampleHome extends StatefulWidget {
  const CameraExampleHome({super.key});
  @override
  CameraExampleHomeState createState() {
    return CameraExampleHomeState();
  }
}

/// Returns a suitable camera icon for [direction].
IconData getCameraLensIcon(CameraLensDirection? direction) {
  switch (direction) {
    case CameraLensDirection.back:
      return Icons.camera_rear;
    case CameraLensDirection.front:
      return Icons.camera_front;
    case CameraLensDirection.external:
    default:
      return Icons.camera;
  }
}

void logError(String code, String message) =>
    print('Error: $code\nError Message: $message');

class CameraExampleHomeState extends State<CameraExampleHome>
    with WidgetsBindingObserver {
  final CameraController controller = CameraController(
    ResolutionPreset.medium,
    enableAudio: true,
    androidUseOpenGL: true,
  );
  String? imagePath;
  String? videoPath;
  VoidCallback? videoPlayerListener;
  bool enableAudio = true; // 是否启用音频
  bool isFlashLight = false; // false表示关闭闪光灯,true表示打开闪光灯
  CameraDescription? _cameraDesc;
  final TextEditingController _textFieldController =
      TextEditingController(text: "rtmp://192.168.1.15/live/live");

  bool get isStreaming => controller.value.isStreamingVideoRtmp ?? false;

  bool get isControllerInitialized => controller.value.isInitialized ?? false;
  bool get isRecordingVideo => controller.value.isRecordingVideo ?? false;
  bool get isRecordingPaused => controller.value.isRecordingPaused;
  bool get isStreamingPaused => controller.value.isStreamingPaused;
  bool get isTakingPicture => controller.value.isTakingPicture ?? false;

  @override
  void initState() {
    onInit();
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    onDispose();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  //
  void onDispose() async {
    await WakelockPlus.disable();
    await controller.dispose();
  }
  // @override
  // Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
  //   // App state changed before we got the chance to initialize.
  //   if (!isControllerInitialized) {
  //     return;
  //   }
  //   if (state == AppLifecycleState.paused) {

  //     await pauseVideoRecording();
  //   } else if (state == AppLifecycleState.resumed) {
  //     await resumeVideoRecording();
  //   }
  // }

  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    Color color = Colors.grey;

    if (isRecordingVideo) {
      color = Colors.redAccent;
    } else if (isStreaming) {
      color = Colors.blueAccent;
    }

    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: const Text('Camera example'),
        actions: Platform.isAndroid ? [
          ElevatedButton(onPressed: isControllerInitialized ? ()async{
            await controller.setFilter(0);
          } : null, child: Text("set filter")),
          ElevatedButton(onPressed: isControllerInitialized ? ()async{
            await controller.removeFilter(0);
          } : null, child: Text("remove filter")),
        ] : null,
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            flex: 2,
            child: Container(
              decoration: BoxDecoration(
                color: Colors.black,
                border: Border.all(
                  color: color,
                  width: 3.0,
                ),
              ),
              child: Center(
                child: _cameraPreviewWidget(),
              ),
            ),
          ),
          Expanded(
            flex: 1,
            child: _captureControlRowWidget(),
          )
        ],
      ),
       floatingActionButton: FloatingActionButton(
          onPressed: isControllerInitialized  ? () async{
            await controller.dispose();
          } : null,
          child: Icon(Icons.close),
        ),
    );
  }

  /// Display camera preview (or a message if the preview is not available).
  Widget _cameraPreviewWidget() {
    if (!isControllerInitialized) {
      return const Text(
        'Tap a camera',
        style: TextStyle(
          color: Colors.white,
          fontSize: 24.0,
          fontWeight: FontWeight.w900,
        ),
      );
    }

    return AspectRatio(
      aspectRatio: controller.value.aspectRatio,
      child: CameraPreview(controller),
    );
  }

  /// Display the thumbnail of the captured image or video.
  Widget _thumbnailWidget() {
    return Align(
      alignment: Alignment.centerRight,
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          imagePath == null
              ? Container()
              : SizedBox(
                  width: 64.0,
                  height: 64.0,
                  child: Image.file(File(imagePath!)),
                ),
        ],
      ),
    );
  }

  /// Display the control bar with buttons to take pictures and record videos.
  Widget _captureControlRowWidget() {
    if (!isControllerInitialized) return Container();

    return ListView(
      children: <Widget>[
        // Only Android has implemented it
        if (Platform.isAndroid)
          ElevatedButton.icon(
            icon: const Icon(
              Icons.camera_alt,
              color: Colors.white,
            ),
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.blue,
            ),
            label: Text(
              "Take Picture",
              style: TextStyle(color: Colors.white),
            ),
            onPressed:
                (isControllerInitialized && (isRecordingVideo || isStreaming)) ? onTakePictureButtonPressed : null,
          ),
        SizedBox(
          width: 5,
        ),
        // Record Localhost video
        ElevatedButton.icon(
          icon: Icon(
            !isRecordingVideo ? Icons.videocam : Icons.stop,
            color: Colors.white,
          ),
          style: ElevatedButton.styleFrom(
            backgroundColor: !isRecordingVideo ? Colors.blue : Colors.red,
          ),
          label: Text(
            !isRecordingVideo ? "Start Record" : "Stop Record",
            style: TextStyle(color: Colors.white),
          ),
          onPressed: isControllerInitialized
              ? () {
                  if (!isRecordingVideo) {
                    onVideoRecordButtonPressed();
                  } else {
                    stopVideoRecording();
                  }
                }
              : null,
        ),
        SizedBox(
          width: 5,
        ),
        ElevatedButton.icon(
          icon: Icon(
            !isStreaming ? Icons.play_arrow : Icons.stop,
            color: Colors.white,
          ),
          style: ElevatedButton.styleFrom(
            backgroundColor: !isStreaming ? Colors.blue : Colors.red,
          ),
          label: Text(
            !isStreaming ? "Start Streaming" : "Stop Streaming",
            style: TextStyle(color: Colors.white),
          ),
          onPressed: isControllerInitialized
              ? () {
                  if (!isStreaming) {
                    startVideoStreaming();
                  } else {
                    stopVideoStreaming();
                  }
                }
              : null,
        ),
        // Only Android has implemented it
        if (Platform.isAndroid)
          ElevatedButton.icon(
              icon: Icon(
                isRecordingPaused ? Icons.play_arrow : Icons.pause,
                color: Colors.white,
              ),
              style: ElevatedButton.styleFrom(
                backgroundColor: !isStreamingPaused ? Colors.blue : Colors.red,
              ),
              label: Text(
                !isRecordingPaused ? "pause Recording" : "resume Recording",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: isControllerInitialized && isRecordingVideo
                  ? () async {
                      if (isRecordingPaused) {
                        await resumeVideoRecording();
                      } else {
                        await pauseVideoRecording();
                      }
                    }
                  : null),
        // stop all Streaming and Recording
        ElevatedButton.icon(
          icon: Icon(
            !(isRecordingVideo && isStreaming) ? Icons.play_arrow : Icons.stop,
            color: Colors.white,
          ),
          style: ElevatedButton.styleFrom(
            backgroundColor:
                !(isRecordingVideo && isStreaming) ? Colors.blue : Colors.red,
          ),
          label: Text(
            !(isRecordingVideo && isStreaming)
                ? "Start Streaming And Recording"
                : "Stop Streaming And Recording",
            style: TextStyle(color: Colors.white),
          ),
          onPressed: isControllerInitialized
              ? () {
                  if (isRecordingVideo && isStreaming) {
                    stopRecordingOrStreaming();
                  } else {
                    onRecordingAndVideoStreamingButtonPressed();
                  }
                }
              : null,
        ),
        _cameraTogglesRowWidget(),
        const SizedBox(
          width: 5,
        ),
        Text('${enableAudio ? 'Enable' : 'Disable'} Audio'),
        Switch(
          value: enableAudio,
          onChanged: (bool value) async {
            if (isControllerInitialized) {
              await controller.switchAudio(value);
              setState(() {
                enableAudio = value;
              });
            } else {
              showInSnackBar('Please select a camera first.');
            }
          },
        ),
        const SizedBox(
          width: 5,
        ),
        Text('${isFlashLight ? 'Enable' : 'Disable'} FlashLight'),
        Switch(
          value: isFlashLight,
          onChanged: (bool value) async {
            if (isControllerInitialized &&
                _cameraDesc?.lensDirection == CameraLensDirection.back) {
                   setState(() {
                isFlashLight = value;
              });
              await controller.switchFlashLight(value);
             
            } else {
              showInSnackBar('Please select a camera first.');
            }
          },
        ),
        
        _thumbnailWidget(),
      ],
    );
  }

  // switch cameras
  void onSwitchCameras(CameraDescription? cld) async {
    if (cld == null) {
      showInSnackBar("camersa not Empty");
      return;
    }
    try {
       setState(() {
        _cameraDesc = cld;
      });
      await controller.switchCamera(cld.name!);
      await WakelockPlus.enable();
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  /// Display a row of toggles to select the camera (or a message if no camera is available).
  Widget _cameraTogglesRowWidget() {
    if (cameras.isEmpty) {
      return Text('No camera found');
    } else {
      return RadioGroup<CameraDescription>(
        groupValue: _cameraDesc,
        onChanged: (CameraDescription? cld) {
          if (isControllerInitialized) {
            onSwitchCameras(cld);
          }
        },
        child: Column(
          children: cameras.map((cameraDescription) {
            return SizedBox(
              width: 90.0,
              child: RadioListTile<CameraDescription>(
                title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
                value: cameraDescription,
              ),
            );
          }).toList(),
        ),
      );
    }
  }

  String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();

  void showInSnackBar(String message) {
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }

  //init
  void onInit() {
    if (cameras.isEmpty) {
      showInSnackBar("No available cameras");
      return;
    }
    var cameraItem = cameras[0];
    setState(() {
      _cameraDesc = cameraItem;
    });
    // init cameras index 0
    onNewCameraSelected(cameraItem);
  }

  //init camera
  void onNewCameraSelected(CameraDescription? cameraDescription) async {
    if (cameraDescription == null) return;
    if (Platform.isMacOS == false) {
      await Permission.camera.request();
      await Permission.microphone.request();
    }
    try {
      await controller.initialize(cameraDescription);
    } on CameraException catch (e) {
      _showCameraException(e);
    }
    // If the controller is updated then update the UI.
    controller.addListener(() {
      if (mounted) setState(() {});

      if (controller.value.event != null) {
        final Map<dynamic, dynamic> event =
            controller.value.event as Map<dynamic, dynamic>;
        final String eventType = event['eventType'] as String;
        //只有发生错误的时候才
        if ((eventType == "error" || eventType == 'rtmp_stopped') &&
            isStreaming) {
          showInSnackBar('Camera error ${controller.value.errorDescription}');
          stopVideoStreaming();
        } else {
          print('Event $event');
          showInSnackBar('Camera message ${controller.value.errorDescription}');
        }
      }
    });

    if (mounted) {
      setState(() {});
    }
  }

  void onTakePictureButtonPressed() async {
    if (!isControllerInitialized) {
      showInSnackBar('Error:  not init');
      return;
    }
    final Directory? extDir = Platform.isAndroid
        ? await getExternalStorageDirectory()
        : await getTemporaryDirectory();
    if (extDir == null) {
      return;
    }
    final String dirPath = '${extDir.path}/Pictures/flutter_test';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.jpg';

    if (isTakingPicture) {
      showInSnackBar('Error:  current is TakingPicture');
      // A capture is already pending, do nothing.
      return;
    }

    try {
      await controller.takePicture(filePath);
      if (mounted) {
        setState(() {
          imagePath = filePath;
        });
        showInSnackBar('Picture saved to $filePath');
      }
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  void onVideoRecordButtonPressed() async {
    if (!isControllerInitialized) {
      showInSnackBar('Error: not init');
      return;
    }

    final Directory? extDir = Platform.isAndroid
        ? await getExternalStorageDirectory()
        : await getTemporaryDirectory();
    if (extDir == null) return;

    final String dirPath = '${extDir.path}/Movies/flutter_test';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.mp4';

    if (isRecordingVideo) {
      // A recording is already started, do nothing.
      showInSnackBar('Error: Recording Video');
      return;
    }

    try {
      videoPath = filePath;
      await controller.startVideoRecording(filePath);
      showInSnackBar('Saving video to $filePath');
      await WakelockPlus.enable();
    } on CameraException catch (e) {
      _showCameraException(e);
      return null;
    }
  }

  void startVideoStreaming() async {
    if (!isControllerInitialized) {
      showInSnackBar('Error: select a camera first.');
      return;
    }

    if (isStreaming) {
      showInSnackBar('Error: is Streaming');
      return;
    }

    // Open up a dialog for the url
    String myUrl = await _getUrl();
    if (myUrl.isEmpty) {
      showInSnackBar('url is empty');
      return;
    }
    try {
      await controller.startVideoStreaming(myUrl);
      showInSnackBar('Streaming video to $myUrl');
      await WakelockPlus.enable();
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  void onRecordingAndVideoStreamingButtonPressed() async {
    if (!isControllerInitialized) {
      showInSnackBar('Error: not init');
      return;
    }

    if (isStreaming) {
      showInSnackBar('Error: is Streaming');
      return;
    }

    String myUrl = await _getUrl();
    if (myUrl.isEmpty) {
      showInSnackBar('url is empty');
      return;
    }
    final Directory? extDir = Platform.isAndroid
        ? await getExternalStorageDirectory()
        : await getTemporaryDirectory();
    if (extDir == null) {
      return;
    }
    final String dirPath = '${extDir.path}/Movies/flutter_test';
    await Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.mp4';

    try {
      videoPath = filePath;
      await controller.startVideoRecordingAndStreaming(videoPath!, myUrl);
      showInSnackBar('Recording streaming video to $myUrl');
      await WakelockPlus.enable();
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  // stop video
  void stopRecordingOrStreaming() async {
    if (!isStreaming && !isRecordingVideo) {
      showInSnackBar('Video stop streamed or recording');
      return;
    }
    try {
      await controller.stopRecordingOrStreaming();
      await WakelockPlus.disable();
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  // stop Video Recording
  void stopVideoRecording() async {
    if (!isRecordingVideo) {
      showInSnackBar('not Start Recording Video');
      return;
    }
    try {
      await controller.stopVideoRecording();
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  /// pause Video Recording
  /// Only Android has implemented it
  Future<void> pauseVideoRecording() async {
    try {
      if (!isRecordingVideo) {
        showInSnackBar('not Start Video recording');
        return;
      }
      await controller.pauseVideoRecording();
      showInSnackBar('Video recording paused');
    } on CameraException catch (e) {
      _showCameraException(e);
      rethrow;
    } catch (e) {
      showInSnackBar(e.toString());
    }
  }

  /// resume Video Recording
  /// Only Android has implemented it
  Future<void> resumeVideoRecording() async {
    try {
      if (!isRecordingVideo) {
        showInSnackBar('not Start Video recording');
        return;
      }
      await controller.resumeVideoRecording();
      showInSnackBar('Video recording resume');
    } on CameraException catch (e) {
      _showCameraException(e);
      rethrow;
    } catch (e) {
      showInSnackBar(e.toString());
    }
  }


  

  Future<String> _getUrl() async {
    // Open up a dialog for the url
    String result = _textFieldController.text;

    return await showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text('Url to Stream to'),
            content: TextField(
              controller: _textFieldController,
              decoration: InputDecoration(hintText: "Url to Stream to"),
              onChanged: (String str) => result = str,
            ),
            actions: <Widget>[
              TextButton(
                child:
                    Text(MaterialLocalizations.of(context).cancelButtonLabel),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
              TextButton(
                child: Text(MaterialLocalizations.of(context).okButtonLabel),
                onPressed: () {
                  Navigator.pop(context, result);
                },
              )
            ],
          );
        });
  }

  void stopVideoStreaming() async {
    if (!isControllerInitialized) {
      showInSnackBar('Error: not init');
      return;
    }
    if (!isStreaming) {
      showInSnackBar('Error: not is Streaming');
      return;
    }

    try {
      await controller.stopVideoStreaming();
    } on CameraException catch (e) {
      _showCameraException(e);
      return;
    }
  }

  //
  void _showCameraException(CameraException e) {
    logError(e.code, e.description ?? "No description found");
    showInSnackBar(
        'Error: ${e.code}\n${e.description ?? "No description found"}');
  }
}

class CameraApp extends StatelessWidget {
  const CameraApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CameraExampleHome(),
    );
  }
}

List<CameraDescription> cameras = [];

Future<void> main() async {
  // Fetch the available cameras before initializing the app.
  try {
    WidgetsFlutterBinding.ensureInitialized();
    cameras = await availableCameras();
    print("$cameras");
  } on CameraException catch (e) {
    logError(e.code, e.description ?? "No description found");
  }
  runApp(CameraApp());
}
4
likes
150
points
135
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Camera and Microphone streaming library via RTMP.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on rtmp_streaming

Packages that implement rtmp_streaming