Flutter媒体处理插件sariska_media_flutter_sdk的使用

Flutter媒体处理插件sariska_media_flutter_sdk的使用

Sariska Media Flutter SDK 提供了强大的 Dart API,用于开发实时音频和视频通话应用程序。

概览

从通信到流媒体再到互动性,SARISKA 提供了一个全面的技术堆栈,帮助您构建从小型应用到大型世界的一切。通过简单、快速且安全的 API 和 SDK,您可以从开发到发布仅需一周时间。

开始使用

Sariska 的 Media SDK for Flutter 是开发可扩展的实时视频应用的最简单方式。此 SDK 支持在 Android 和 iOS 应用中仅需六个步骤即可完成开发:

  1. 初始化 SDK
  2. 创建连接
  3. 创建会议
  4. 捕获本地音视频轨道
  5. 捕获远程音视频轨道
  6. 播放音视频轨道

如需详细了解如何将 SDK 集成到您的实时视频应用中,请参阅 此处

如何运行示例应用

  1. 前往 example/lib/Generatetoken.dart
  2. 将您的 API 密钥放置在相应位置:
    'apiKey': "{Your-api-key}",
    
  3. 前往 example/lib/main.dart
  4. 运行 flutter pub get
  5. 运行应用
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_audio_output/flutter_audio_output.dart';
import 'package:flutter_iconly/flutter_iconly.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:sariska_media_flutter_sdk/Conference.dart';
import 'package:sariska_media_flutter_sdk/Connection.dart';
import 'package:sariska_media_flutter_sdk/JitsiLocalTrack.dart';
import 'package:sariska_media_flutter_sdk/JitsiRemoteTrack.dart';
import 'package:sariska_media_flutter_sdk/SariskaMediaTransport.dart';
import 'package:sariska_media_flutter_sdk/WebRTCView.dart';
import 'package:sariska_media_flutter_sdk_example/GenerateToken.dart';

typedef LocalTrackCallback = void Function(List<JitsiLocalTrack> tracks);
const Color themeColor = Color(0xff4050B5);

void main() {
  runApp(const MaterialApp(
    home: RoomNamePage(),
    debugShowCheckedModeBanner: false,
  ));
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key, required this.roomName}) : super(key: key);

  final String roomName;

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
  static late LocalTrackCallback localTrackCallback;
}

class _MyAppState extends State<MyApp> {
  final _sariskaMediaTransport = SariskaMediaTransport();
  String token = 'unknown';
  String streamURL = '';
  List<JitsiRemoteTrack> remoteTracks = [];
  List<JitsiLocalTrack> localtracks = [];
  List<Map<String, String>> lobbyUsers = [];
  JitsiLocalTrack? localTrack;
  bool isAudioOn = true;
  bool isVideoOn = true;

  late Conference _conference;
  late Connection _connection;

  [@override](/user/override)
  void initState() {
    super.initState();
    initPlatformState();
    init();
  }

  [@override](/user/override)
  void dispose() {
    _timer!.cancel();
    super.dispose();
  }

  late AudioInput _currentInput = const AudioInput("unknown", 0);
  bool isSpeakerOn = false;
  bool isCameraSwitch = false;

  Future<void> init() async {
    FlutterAudioOutput.setListener(() async {
      await _getInput();
      setState(() {});
    });

    await _getInput();
    if (!mounted) return;
    setState(() {});
  }

  _getInput() async {
    _currentInput = await FlutterAudioOutput.getCurrentOutput();
  }

  Timer? _timer;
  bool hasJoinedLobby = false;
  bool isLoading = false;
  bool flag = false;

  late BuildContext currentContext;

  void startLobbyTimeout() {
    _timer = Timer(const Duration(seconds: 7), () {
      setState(() {
        isLoading = false;
      });

      if (flag) return;

      for (JitsiLocalTrack x in localtracks) {
        x.dispose();
      }
      localTrack?.dispose();
      localtracks.clear();

      _conference.leave();
      _connection.disconnect();
      _conference.removeEventListener("CONFERENCE_FAILED");
      _conference.removeEventListener("TRACK_ADDED");
      _conference.removeEventListener("CONFERENCE_JOINED");
      _conference.removeEventListener("USER_ROLE_CHANGED");
      _conference.removeEventListener("MESSAGE_RECEIVED");
      _conference.removeEventListener("LOBBY_USER_JOINED");
      _connection.removeEventListener("CONNECTION_ESTABLISHED");
      _connection.removeEventListener("CONNECTION_FAILED");
      _connection.removeEventListener("CONNECTION_FAILED");

      Navigator.pop(currentContext);
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    currentContext = context;
    return MaterialApp(
      home: Scaffold(
        body: Stack(
          children: [
            if (localTrack != null && isVideoOn)
              Positioned(
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                child: WebRTCView(
                  localTrack: localTrack!,
                  mirror: true,
                  objectFit: 'cover',
                ),
              ),
            if (localTrack != null && !isVideoOn)
              const Positioned(
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                child: Center(
                  child: Icon(
                    IconlyLight.profile,
                    size: 100,
                    color: Colors.white,
                  ),
                ),
              ),
            Positioned(
              bottom: 140,
              left: 0,
              right: 0,
              child: SizedBox(
                height: 100,
                child: ListView.builder(
                  scrollDirection: Axis.horizontal,
                  itemCount: remoteTracks.length,
                  itemBuilder: (BuildContext context, int index) {
                    return Padding(
                      padding: const EdgeInsets.only(left: 2.0, right: 2.0),
                      child: Container(
                        width: 120,
                        height: 100,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(30),
                        ),
                        child: WebRTCView(
                          localTrack: remoteTracks[index],
                          mirror: true,
                          objectFit: 'cover',
                        ),
                      ),
                    );
                  },
                ),
              ),
            ),
            if (isLoading)
              Positioned(
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                child: BackdropFilter(
                  filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
                  child: Container(
                    color: Colors.black.withOpacity(0.5),
                    child: const Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          CircularProgressIndicator(),
                          SizedBox(
                            height: 16,
                          ),
                          Text(
                            'Joining lobby...',
                            style: TextStyle(color: Colors.white, fontSize: 18),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            Positioned(
              bottom: 0,
              left: 0,
              right: 0,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Container(
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(30),
                  ),
                  child: Material(
                    borderRadius: BorderRadius.circular(30),
                    clipBehavior: Clip.antiAlias,
                    color: Colors.transparent,
                    child: BackdropFilter(
                      filter: ImageFilter.blur(
                        sigmaY: 20,
                        sigmaX: 20,
                      ),
                      child: InkWell(
                        highlightColor: Colors.transparent,
                        splashColor: Colors.transparent,
                        child: Container(
                          decoration: BoxDecoration(
                            color: Colors.white.withAlpha(30),
                          ),
                          padding: const EdgeInsets.symmetric(
                            horizontal: 14,
                            vertical: 20,
                          ),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              buildCustomButton(
                                onPressed: () {
                                  setState(() {
                                    for (JitsiLocalTrack track in localtracks) {
                                      if (track.getType() == "audio") {
                                        if (isAudioOn) {
                                          track.mute();
                                          isAudioOn = !isAudioOn;
                                        } else {
                                          track.unmute();
                                          isAudioOn = !isAudioOn;
                                        }
                                        break;
                                      }
                                    }
                                  });
                                },
                                icon: isAudioOn
                                    ? IconlyLight.voice
                                    : Icons.mic_off_outlined,
                                color: Colors.transparent,
                              ),
                              buildCustomButton(
                                onPressed: () {
                                  setState(() {
                                    for (JitsiLocalTrack track in localtracks) {
                                      if (track.getType() == "video") {
                                        track.switchCamera();
                                        isCameraSwitch = !isCameraSwitch;
                                      }
                                    }
                                  });
                                  setState(() {});
                                },
                                icon: isCameraSwitch
                                    ? IconlyLight.camera
                                    : Icons.switch_camera_outlined,
                                color: Colors.transparent,
                              ),
                              buildEndCallButton(
                                onPressed: () {
                                  for (JitsiLocalTrack x in localtracks) {
                                    x.dispose();
                                  }
                                  localTrack?.dispose();
                                  localtracks.clear();
                                  _conference.leave();
                                  _connection.disconnect();
                                  _conference
                                      .removeEventListener("CONFERENCE_FAILED");
                                  _conference
                                      .removeEventListener("TRACK_ADDED");
                                  _conference
                                      .removeEventListener("CONFERENCE_JOINED");
                                  _conference
                                      .removeEventListener("USER_ROLE_CHANGED");
                                  _conference
                                      .removeEventListener("MESSAGE_RECEIVED");
                                  _conference
                                      .removeEventListener("LOBBY_USER_JOINED");
                                  _connection.removeEventListener(
                                      "CONNECTION_ESTABLISHED");
                                  _connection
                                      .removeEventListener("CONNECTION_FAILED");
                                  _connection
                                      .removeEventListener("CONNECTION_FAILED");
                                  Navigator.pop(context);
                                },
                              ),
                              buildCustomButton(
                                onPressed: () {
                                  setState(() {
                                    for (JitsiLocalTrack track in localtracks) {
                                      if (track.getType() == "video") {
                                        if (isVideoOn) {
                                          track.mute();
                                          isVideoOn = !isVideoOn;
                                        } else {
                                          track.unmute();
                                          isVideoOn = !isVideoOn;
                                        }
                                        break;
                                      }
                                    }
                                  });
                                },
                                icon: isVideoOn
                                    ? IconlyLight.video
                                    : Icons.videocam_off_outlined,
                                color: Colors.transparent,
                              ),
                              buildCustomButton(
                                onPressed: () async {
                                  if (isSpeakerOn) {
                                    toggleSpeaker(false);
                                  } else {
                                    toggleSpeaker(true);
                                  }
                                },
                                icon: isSpeakerOn
                                    ? IconlyLight.volumeUp
                                    : IconlyBold.volumeOff,
                                color: Colors.transparent,
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
        backgroundColor: Colors.black,
      ),
    );
  }

  Future<void> initPlatformState() async {
    try {
      debugPrint("Initializing platform state");
      token = await generateToken();
      _sariskaMediaTransport.initializeSdk();
      setupLocalStream();

      _connection = Connection(token, widget.roomName, false);

      _connection.addEventListener("CONNECTION_ESTABLISHED", () {
        _conference = _connection.initJitsiConference();
        _conference.addEventListener("CONFERENCE_JOINED", () {
          for (JitsiLocalTrack track in localtracks) {
            debugPrint("Local Track Added");
            _conference.addTrack(track);
          }
        });

        _conference.addEventListener("TRACK_MUTE_CHANGED", (track) {
          debugPrint("TRACK_MUTE_CHANGED Event called");
        });

        _conference.addEventListener("USER_ROLE_CHANGED", (id, newRole) {
          debugPrint("User Role changed");
          String role = newRole.toString().toLowerCase();
          if (role == "moderator") {
            debugPrint("Enable Lobby Called");
            _conference.enableLobby();
          }
        });

        _conference.addEventListener("MESSAGE_RECEIVED", (senderId, message) {
          debugPrint("Received Message $message");
        });

        _conference.addEventListener("LOBBY_USER_JOINED", (id, displayName) {
          debugPrint("New User in Lobby");
          debugPrint(id);
          debugPrint(displayName);
          lobbyUsers.add({'id': id, 'name': displayName});
          setState(() {
            showDialog(
              context: currentContext,
              builder: (BuildContext currentContext) {
                return AlertDialog(
                  title: const Text('A New User want to join lobby ?'),
                  content: const Text(
                    '',
                  ),
                  actions: <Widget>[
                    TextButton(
                      style: TextButton.styleFrom(
                        textStyle:
                            Theme.of(currentContext).textTheme.labelLarge,
                      ),
                      child: const Text('Deny user'),
                      onPressed: () {
                        _conference.lobbyDenyAccess(id.toString());
                        Navigator.of(currentContext).pop();
                      },
                    ),
                    TextButton(
                      style: TextButton.styleFrom(
                        textStyle:
                            Theme.of(currentContext).textTheme.labelLarge,
                      ),
                      child: const Text('Allow user'),
                      onPressed: () {
                        _conference.lobbyApproveAccess(id.toString());
                        Navigator.of(currentContext).pop();
                      },
                    ),
                  ],
                );
              },
            );
          });
        });

        _conference.addEventListener("LOBBY_USER_LEFT", (id) {
          debugPrint("Left User from Lobby");
        });

        _conference.addEventListener("CONFERENCE_FAILED", () {
          debugPrint("Conference Failed call in flutter");
          if (!hasJoinedLobby) {
            hasJoinedLobby = true;
            setState(() {
              isLoading = true;
            });
            startLobbyTimeout();
            _conference.joinLobby(_conference.getUserName(), "random_email");
          }
        });

        _conference.addEventListener("TRACK_ADDED", (track) {
          JitsiRemoteTrack remoteTrack = track;
          debugPrint('The Remotetrack is: $remoteTrack');
          for (JitsiLocalTrack track in localtracks) {
            if (track.getStreamURL() == remoteTrack.getStreamURL()) {
              return;
            }
          }
          if (remoteTrack.getType() == "audio") {
            return;
          }
          streamURL = remoteTrack.getStreamURL();
          replaceChild(remoteTrack);
        });
        _conference.join();
      });

      _connection.addEventListener("CONNECTION_FAILED", () {
        print("Connection Failed");
      });

      _connection.addEventListener("CONNECTION_DISCONNECTED", () {
        print("Connection Disconnected");
      });

      _connection.connect();

      setState(() {});
    } on PlatformException {
      print("Failed to get platform version.");
    }
  }

  void setupLocalStream() {
    Map<String, dynamic> options = {};
    options["audio"] = true;
    options["video"] = true;

    _sariskaMediaTransport.createLocalTracks(options, (tracks) {
      localtracks = tracks;
      for (JitsiLocalTrack track in localtracks) {
        if (track.getType() == "video") {
          setState(() {
            localTrack = track;
          });
        }
      }
    });
  }

  void replaceChild(JitsiRemoteTrack remoteTrack) {
    setState(() {
      remoteTracks.add(remoteTrack);
    });
    if (_timer!.isActive) {
      _timer!.cancel();
      setState(() {
        isLoading = false;
      });
    }
    flag = true;
  }

  void toggleSpeaker(bool isSpeaker) async {
    for (JitsiLocalTrack track in localtracks) {
      if (track.getType() == "audio") {
        track.toggleSpeaker(isSpeaker);
        isSpeakerOn = isSpeaker;
        setState(() {});
      }
    }
  }

  Widget buildCustomButton({
    required VoidCallback onPressed,
    required IconData icon,
    required Color color,
  }) {
    return Container(
      decoration: const BoxDecoration(
        shape: BoxShape.circle,
        color: Colors.transparent,
      ),
      child: InkWell(
        onTap: onPressed,
        customBorder: const CircleBorder(),
        child: Container(
          padding: const EdgeInsets.all(10),
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: color,
          ),
          child: Icon(
            icon,
            color: Colors.black,
            size: 30,
          ),
        ),
      ),
    );
  }

  Widget buildEndCallButton({required VoidCallback onPressed}) {
    return Container(
      decoration: const BoxDecoration(
        shape: BoxShape.circle,
        color: Colors.red,
      ),
      child: InkWell(
        onTap: onPressed,
        customBorder: const CircleBorder(),
        child: Container(
          padding: const EdgeInsets.all(15),
          child: const Icon(
            Icons.call_end,
            color: Colors.white,
            size: 40,
          ),
        ),
      ),
    );
  }
}

class RoomNamePage extends StatefulWidget {
  const RoomNamePage({Key? key}) : super(key: key);

  [@override](/user/override)
  _RoomNamePageState createState() => _RoomNamePageState();
}

class _RoomNamePageState extends State<RoomNamePage> {
  final TextEditingController _roomNameController = TextEditingController();

  [@override](/user/override)
  void initState() {
    super.initState();
    webViewMethod();
  }

  Future webViewMethod() async {
    print('In Microphone permission method');
    WidgetsFlutterBinding.ensureInitialized();
    await Permission.microphone.request();
    WebViewMethodForCamera();
  }

  Future WebViewMethodForCamera() async {
    print('In Camera permission method');
    WidgetsFlutterBinding.ensureInitialized();
    await Permission.camera.request();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Material(
      type: MaterialType.transparency, // or any other theme configuration
      child: Scaffold(
        appBar: AppBar(
          title: const Text(
            'Sariska.io',
            style: TextStyle(
              color: themeColor,
              fontSize: 30,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              const SizedBox(height: 20),
              TextField(
                controller: _roomNameController,
                decoration: const InputDecoration(
                  prefixIcon: Icon(IconlyLight.video),
                  contentPadding: EdgeInsets.symmetric(horizontal: 15),
                  focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(color: themeColor, width: 2),
                  ),
                  labelText: "Room Name",
                  labelStyle: TextStyle(
                    color: Colors.grey,
                    fontSize: 14,
                  ),
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  backgroundColor: themeColor,
                  textStyle: const TextStyle(color: Colors.white),
                ),
                onPressed: () {
                  debugPrint("Meeting create button pressed");
                  final roomName = _roomNameController.text.trim();
                  if (roomName.isNotEmpty) {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => MyApp(roomName: roomName),
                      ),
                    );
                  } else {
                    Fluttertoast.showToast(
                        msg: "Please Enter a room name",
                        toastLength: Toast.LENGTH_SHORT,
                        gravity: ToastGravity.BOTTOM,
                        timeInSecForIosWeb: 1,
                        backgroundColor: Colors.yellow,
                        textColor: Colors.white,
                        fontSize: 16.0);
                  }
                },
                child: const Text(
                  'Enter Room',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 20,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter媒体处理插件sariska_media_flutter_sdk的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter媒体处理插件sariska_media_flutter_sdk的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用 sariska_media_flutter_sdk 插件的示例代码。这个插件通常用于媒体处理任务,比如视频录制、编辑等。请注意,由于我无法直接访问该插件的官方文档或源代码,以下代码是基于假设的功能编写的,并且可能需要根据实际的插件API进行调整。

首先,确保你已经在 pubspec.yaml 文件中添加了该插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  sariska_media_flutter_sdk: ^latest_version  # 替换为实际的最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,下面是一个简单的示例,展示如何使用该插件进行视频录制:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Sariska Media Flutter SDK Demo'),
        ),
        body: Center(
          child: RecordVideoButton(),
        ),
      ),
    );
  }
}

class RecordVideoButton extends StatefulWidget {
  @override
  _RecordVideoButtonState createState() => _RecordVideoButtonState();
}

class _RecordVideoButtonState extends State<RecordVideoButton> {
  final SariskaMediaFlutterSdk _mediaSdk = SariskaMediaFlutterSdk();
  String _videoPath;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        // 开始录制视频
        final recordResult = await _mediaSdk.startVideoCapture(
          outputFile: 'video_${DateTime.now().millisecondsSinceEpoch}.mp4',
          maxDuration: Duration(seconds: 30), // 最大录制时长
        );

        if (recordResult.isSuccess) {
          setState(() {
            _videoPath = recordResult.videoPath;
          });
          print('Video recorded successfully: $_videoPath');
        } else {
          print('Failed to record video: ${recordResult.error}');
        }
      },
      child: Text('Record Video'),
    );
  }

  @override
  void dispose() {
    // 释放资源
    _mediaSdk.dispose();
    super.dispose();
  }
}

在这个示例中,我们创建了一个简单的 Flutter 应用,其中包含一个按钮,用于触发视频录制。SariskaMediaFlutterSdk 类的 startVideoCapture 方法用于开始视频录制,并返回一个包含录制结果的对象。如果录制成功,我们可以获取到录制的视频路径。

请注意以下几点:

  1. 错误处理:在实际应用中,你应该添加更多的错误处理逻辑,以处理各种可能的异常情况。
  2. 权限请求:在录制视频之前,你需要确保应用已经获得了必要的权限(如相机和存储权限)。这通常需要在 Android 和 iOS 的原生代码中进行配置。
  3. 插件API:由于我无法访问实际的插件文档,上述代码中的方法名和参数可能需要根据实际插件的API进行调整。

最后,请查阅 sariska_media_flutter_sdk 的官方文档以获取更详细的信息和最新的API说明。

回到顶部