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 应用中仅需六个步骤即可完成开发:
- 初始化 SDK
- 创建连接
- 创建会议
- 捕获本地音视频轨道
- 捕获远程音视频轨道
- 播放音视频轨道
如需详细了解如何将 SDK 集成到您的实时视频应用中,请参阅 此处。
如何运行示例应用
- 前往
example/lib/Generatetoken.dart
- 将您的 API 密钥放置在相应位置:
'apiKey': "{Your-api-key}",
- 前往
example/lib/main.dart
- 运行
flutter pub get
- 运行应用
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
更多关于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
方法用于开始视频录制,并返回一个包含录制结果的对象。如果录制成功,我们可以获取到录制的视频路径。
请注意以下几点:
- 错误处理:在实际应用中,你应该添加更多的错误处理逻辑,以处理各种可能的异常情况。
- 权限请求:在录制视频之前,你需要确保应用已经获得了必要的权限(如相机和存储权限)。这通常需要在 Android 和 iOS 的原生代码中进行配置。
- 插件API:由于我无法访问实际的插件文档,上述代码中的方法名和参数可能需要根据实际插件的API进行调整。
最后,请查阅 sariska_media_flutter_sdk
的官方文档以获取更详细的信息和最新的API说明。