音视频播放
更新时间:2026-04-03 11:20:37
media 提供设备的直播,点播,截图,录制,音频对讲等相关功能
// 导入头文件
import 'package:airtc_engine/src/media/rtcx_media_player.dart';
import 'package:airtc_engine/src/audio_video_talk/rtcx_audio_video_talk.dart';
import 'package:airtc_engine/src/audio_video_talk/rtcx_audio_video_talk_controller.dart';
import 'package:airtc_engine/src/media/player_view.dart';
import 'package:airtc_engine/src/media/player_view_controller.dart';
// 导入头文件
class RTCXFluPlayerCallback {
/// player播放状态回调
///
/// @param playState player状态(RTCXCameraPlayerState)
/// @param code player错误码
void onPlayStateChanged(RTCXFluCameraPlayerState playState, int code);
/// 视频录制回调
///
/// @param currentTime 回调当前录制时间
/// @param filePath 回调数据路径
/// @param error 视频录制错误
void onPlayerMp4Record(int currentTime, String filePath);
/// 当打开player全链路日志开关时,数据返回
///
/// @param data 返回的数据
/// void onPlayerDataCollection(String data);
/// video size 更新回调
///
/// @param size 宽、高
void onPlayFrameChanged(RTCXFluVideoSize size);
/// 播放trace回调(可统计耗时)
///
/// @param playTrace 播放trace(IOTPlayTraces)
/// @param tempstime trace发生时间点
void onPlayTracesChanged(
RTCXFluIOTPlayTraces playTrace,
int tempstime,
String? msg,
);
}
// 播放器控制
class RTCXFluPlayerApi {
/// 播放器相关
void createUIView(int viewId);
/// 释放播放器
void disposeView();
/// 播放器控制相关
void start();
///恢复播放
bool resume();
///暂停
bool pause();
/// 获取帧率 fps
int getPlayFPS();
/// 获取码率 单位bit
int getBitrate();
/// 设置播放器放大倍数
/// 原始放大倍数,初始化为1,最大为4
bool setScaleRation(int scale);
/// 获取缩放比例
double getScaleRation();
/// 获取播放画面Size
RTCXFluVideoSize getVideoSize();
/// 倍速播放
///
/// @param rate 1、2、4等
void setPlaybackSpeed(double rate);
// 设置播放音量
//
// @param volume 取值0-100 其中0:静音 100:最大音量
// void setVolume(int volume);
/// 设置旋转角度
/// 0 90 180 270
/// @param degree 旋转角度
void setVideoRotate(int degree);
/// 设置播放是否静音
///
/// @param mute 是否静音 YES:静音 NO:非静音
void setMuteRemoteAudio(bool mute);
/// 边播边录
///
/// @param path 保存文件夹
/// @param isAdd 是否添加时间水印
bool startRecord(String path, bool addTimeWatermark);
/// 停止边播边录
bool stopRecord();
/// 截图
/// @param bOriginalPic 是否原始尺寸
/// @param isAdd 是否添加时间水印
/// @return image
Uint8List snapshot(bool bOriginalPic, bool addTimeWatermark);
/// 获取播放偏移量,单位毫秒
int getPosition();
/// 获取当前播放时间,单位秒
double getCurrentTime();
}
播放器初始化
调用示例如下:
final PlayerViewController _controller = PlayerViewController();
int rotation = 0;
Uint8List? _snapshotImage; // 存储截图数据
// 语音对讲
final RTCXFluAudioVideoTalkController _talkApi =
RTCXFluAudioVideoTalkController();
@override
void initState() {
super.initState();
// 初始化通话对象
_talkApi.initTalk(
RTCXFluAudioVideoTalkReq(
type: RTCXFluAudioVideoTalkType.RTCXFluAudioVideoTalkType_Video,
iotId: widget.iotId,
productKey: widget.productKey,
deviceName: widget.deviceName,
deviceType: widget.deviceType,
),
);
_talkApi.onTalkError.listen((error) {
print('通话错误: $error');
});
// 注册 _controller 的所有回调监听
_controller.playStateChangeStream.listen((data) {
print(
'播放状态变化: viewId=${data.viewId}, playState=${data.playState}, errorCode=${data.errorCode}',
);
});
_controller.playFrameChangeStream.listen((data) {
print(
'视频尺寸变化: viewId=${data.viewId}, width=${data.videoSize.width}, height=${data.videoSize.height}',
);
});
_controller.playTracesChangeStream.listen((data) {
print(
'播放Trace: viewId=${data.viewId}, playTrace=${data.playTrace}, timestamp=${data.timestamp}, msg=${data.msg}',
);
});
_controller.playerMp4RecordStream.listen((data) {
print(
'录制回调: viewId=${data.viewId}, currentTime=${data.currentTime}, filePath=${data.filePath}',
);
});
// 注册 _controller 的所有回调监听
_controller.playStateChangeStream.listen((data) {
switch(data.playState) {
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateInitial:
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePrepared:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePlaying:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateRenderingStart:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePaused:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateBuffingStart:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateBuffingEnd:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePlaybackCompleted:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateStopped:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateError:
// TODO: Handle this case.
break;
}
print(
'播放状态变化: viewId=${data.viewId}, playState=${data.playState}, errorCode=${data.errorCode}',
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('视频播放')),
body: Column(
children: [
Expanded(
flex: 1,
child: PlayerView(
iotId: widget.iotId,
productKey: widget.productKey,
deviceName: widget.deviceName,
deviceType: widget.deviceType,
controller: _controller,
),
),
]));}
@override
void dispose() {
// 取消所有回调订阅
_controller.disposeView();
_controller.destroy();
_talkApi.release();
super.dispose();
}
直播
调用示例如下:
// 开始直播
_controller.start();
监听播放状态
/// 播放器状态回调
enum RTCXFluCameraPlayerState {
/// 播放初始化播放未开始
RTCXFluCameraPlayerStateInitial,
/// 播放准备就绪
RTCXFluCameraPlayerStatePrepared,
/// 播放状态
RTCXFluCameraPlayerStatePlaying,
/// 开始图像渲染
RTCXFluCameraPlayerStateRenderingStart,
/// 暂停状态
RTCXFluCameraPlayerStatePaused,
/// 缓冲状态开始
RTCXFluCameraPlayerStateBuffingStart,
/// 缓冲状态结束
RTCXFluCameraPlayerStateBuffingEnd,
/// 事件,剪辑视频等,播放完毕
RTCXFluCameraPlayerStatePlaybackCompleted,
/// 播放停止
RTCXFluCameraPlayerStateStopped,
/// 播放错误
RTCXFluCameraPlayerStateError,
}
/// player播放状态回调
///
/// @param playState player状态(RTCXCameraPlayerState)
/// @param code player错误码
void onPlayStateChanged(int viewId, RTCXFluCameraPlayerState playState, int code);
调用示例如下:
// 注册 _controller 的所有回调监听
_controller.playStateChangeStream.listen((data) {
switch(data.playState) {
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateInitial:
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePrepared:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePlaying:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateRenderingStart:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePaused:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateBuffingStart:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateBuffingEnd:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStatePlaybackCompleted:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateStopped:
// TODO: Handle this case.
break;
case RTCXFluCameraPlayerState.RTCXFluCameraPlayerStateError:
// TODO: Handle this case.
break;
}
print(
'播放状态变化: viewId=${data.viewId}, playState=${data.playState}, errorCode=${data.errorCode}',
);
});
播放状态码
| 播放状态 | 状态码&描述 |
|---|---|
| RTCXFluCameraPlayerStateStopped | -501:SD卡回放已经没有更多数据了(回放结束) |
| RTCXFluCameraPlayerStatePlaybackCompleted | -10010:到达了限制播放时长 |
| RTCXFluCameraPlayerStateError | -2:播放流通道连接失败或断开连接 -5:播放流通道连接异常预警 -500:复用P2P播放通道超时 -10025:SD卡回放已达设备最大支持数量 -10028:无效的RTC通道 -10029:P2P播放通道已达设备最大支持数量 -10047:低功耗设备正在进入休眠 100000:CMS流通道连接失败或断开连接 1000001:P2P流通道连接失败或断开连接 1000003:P2P流通道连接超时 1000008:SD卡回放获取SDP失败 1000011:直播获取SDP失败 1000013:直播获取SDP超时 1000014:SD卡回放获取SDP超时 1000016:调用云回看播放地址失败 |
播放器销毁
当不再需要播放的时候,要销毁播放器
/// 释放播放器
void disposeView();
调用示例如下:
// 在Widget的dispose方法中释放播放器资源
// 释放播放器资源
_controller.disposeView();
截图
调用示例如下:
// 截图
try{
Uint8List? image = await _controller.snapshot(true, false);
if (image != null) {
setState(() {
_snapshotImage = image;
});
}
}catch(e) {
print("snapshot e:${e.toString()}");
}
静音
调用示例如下:
// 是否静音 YES/NO
// mute 是否静音 YES:静音 NO:非静音
try{
_controller.setMuteRemoteAudio(true);
}catch(e) {
print("setMuteRemoteAudio e:${e.toString()}");
}
倍速播放
注意
- SD卡非1倍速播放时,是不支持静音设置和视屏录制的
调用示例如下:
// 设置倍速 rate 0.5、1、2、4等
try{
_controller.setPlaybackSpeed(speed);
}catch(e){
print("setPlaybackSpeed e:${e.toString()}");
}
边播边录
边播边录,传入存储的目标文件夹。可使用path_provider获取文件夹目录。
调用示例如下:
// 边播边录
if (Platform.isIOS) {
getApplicationDocumentsDirectory().then((directory){
print("getApplicationDocumentsDirectory directory:${directory}");
// 保存文件夹
_controller.startRecord("${directory.path}", false).then((result) {
print("startRecord result:${result}");
}).catchError((err){
print('startRecord err:${err.toString()}');
});
}).catchError((err){
print('getApplicationDocumentsDirectory err:${err.toString()}');
});
} else if (Platform.isAndroid) {
// 如没有该目录,可使用其他目录
getExternalStorageDirectory().then((directory){
print("getExternalStorageDirectory directory:${directory}");
// 保存文件夹
_controller.startRecord("${directory!}", false).then((result) {
print("startRecord result:${result}");
}).catchError((err){
print('startRecord err:${err.toString()}');
});
}).catchError((err){
print('getExternalStorageDirectory err:${err.toString()}');
});
}
// 停止边播边录
_controller.stopRecord().then((result) {
print("stopRecord result:${result}");
}).catchError((err){
print('stopRecord err:${err.toString()}');
});
// controller的playerMp4RecordStream监听回调获取filepath,
// 录制过程中,filePath为空字符串,结束录制时filePath未具体存储的地址
_controller.playerMp4RecordStream.listen((data) {
print(
'录制回调: viewId=${data.viewId}, currentTime=${data.currentTime}, filePath=${data.filePath}',
);
if(data.filePath != null && data.filePath.isNotEmpty) {
//录制完成
} else {
//录制过程中
}
});
画面旋转
调用示例如下:
// 设置画面旋转角度,degree参数值为0 90 180 270
_controller.setVideoRotate(rotation);
音频通话(语音对讲)
// 导入头文件
import 'package:airtc_engine/src/audio_video_talk/rtcx_audio_video_talk.dart';
import 'package:airtc_engine/src/audio_video_talk/rtcx_audio_video_talk_controller.dart';
class RTCXFluAudioVideoTalkApi {
/// 初始化音视频通话对象
void initTalk(RTCXFluAudioVideoTalkReq req);
/// 开始通话
void startTalk();
/// 结束通话
void endTalk();
/// 设置音视频通话质量
void setAudioVideoTalk(RTCXFluAudioVideoTalkConfig config);
/// 设置麦克风是否静音 YES: 静音 NO:不静音
RTCXFluAudioVideoResult setMuteLocalAudio(bool mute);
/// 设置摄像头是否开启 YES: 关闭摄像头 NO:开启摄像头
RTCXFluAudioVideoResult setMuteLocalVideo(bool mute);
/// 切换前后摄像头 暂时未实现
/// 切换前后摄像头 front暂未启用,前后摄像头切换无需参数控制,completionCallback: 摄像头前后切换完成回调,在此回调之后再进行下一次的切换,防止crash
void setSwitchCamera();
/// 释放音视频通话对象 (SDK没有,但封装后,内部引用,需手动释放)
void release();
}
调用示例如下:
// 1. 创建实例
// 语音对讲
final RTCXFluAudioVideoTalkController _talkApi = RTCXFluAudioVideoTalkController();
// 2. 初始化设置
@override
void initState() {
super.initState();
// 初始化通话对象
_talkApi.initTalk(
RTCXFluAudioVideoTalkReq(
type: RTCXFluAudioVideoTalkType.RTCXFluAudioVideoTalkType_Audio,
iotId: widget.iotId,
productKey: widget.productKey,
deviceName: widget.deviceName,
deviceType: widget.deviceType,
),
);
// 添加监听
_talkApi.onTalkError.listen((error) {
print('通话错误: $error');
});
}
// 释放
@override
void dispose() {
_talkApi.release();
super.dispose();
}
// 开始音频通话
_talkApi.startTalk();
// 结束音频通话
_talkApi.endTalk();
码率
调用示例如下:
// 获取码率
try{
int bitrate = await _controller.getBitrate();
}catch(e) {
print("getBitrate e:${e.toString()}");
}
帧率
调用示例如下:
// 获取帧率
try{
int fps = await _controller.getPlayFPS();
}catch(e) {
print("getPlayFPS e:${e.toString()}");
}
缩放比例
调用示例如下:
// 获取缩放比例
_controller.getScaleRation().then((result){
print('getScaleRation scale:(${result})');
}).catchError((err){
print('getScaleRation err:${err.toString()}');
});
设置缩放比例
调用示例如下:
// 设置缩放比例,原始放大倍数,初始化为1,最大为4
_controller.setScaleRation(scale).then((result){
print('setScaleRation scale:(${result})');
}).catchError((err){
print('setScaleRation err:${err.toString()}');
});
播放偏移量
调用示例如下:
// 获取播放偏移量,单位为毫秒
_controller.getPosition().then((result) {
print("getPosition result:${result}");
}).catchError((err){
print('getPosition err:${err.toString()}');
});
播放时间
调用示例如下:
// 获取当前播放时间,单位秒
_controller.getCurrentTime().then((result) {
print("getCurrentTime result:${result}");
String showTime = DateUtil.formatDateMs((result*1000).toInt(), format: "yyyy/MM/dd HH:mm:ss");
print("getCurrentTime showTime:${showTime}");
}).catchError((err){
print('getCurrentTime err:${err.toString()}');
});
DataChannel通道
DataChannel通道可用于自定义数据透传,如:App端和设备端需要相互传递图片、音视频、文件等自定义数据时,可通过该通道传递数据。
// 引入头文件
import 'package:airtc_engine/src/preconnect/rtcx_preconnect.dart';
class RTCXFluDataChannelApi {
/// 初始化datachannel
void createDataChannel(String uuid,RTCXFluDataChannelModel model);
/// 设置代理
void setOnDataChannelListener(String uuid);
/// 是否已连接
bool isConnect(String uuid);
/// 发送数据
void sendMessage(String uuid,RTCXFluDCCustomDataTransfer customData);
///销毁
void destroy(String uuid);
}
class RTCXFluDataChannelCallback {
/// 设备透传数据回调
void onData(String uuid,RTCXFluDCCustomDataTransfer customData);
/// 通道发生错误回调
void onStatusChange(String uuid,Map<String, Object> error);
}
使用RTCXFluDataChannelController调用api、监听消息
- DataChannel初始化
late RTCXFluDataChannelController _datachannelController;
@override
void initState() {
super.initState();
// datachannel通道
_datachannelController = RTCXFluDataChannelController();
_datachannelController.createDataChannel(
RTCXFluDataChannelModel(
productKey: widget.productKey,
deviceName: widget.deviceName,
iotId: widget.iotId,
deviceType: widget.deviceType,
),
);
_datachannelController.onData.listen((RTCXFluDCCustomDataTransfer event) {
if (event.customBinary != null) {
String content = utf8.decode(event.customBinary!);
setState(() {
contentText = "收到消息: $content";
});
}
});
_datachannelController.onStatusChange.listen((Map<String, Object> p1) {
String jsonString = json.encode(p1);
Fluttertoast.showToast(msg: jsonString);
});
}
- 释放资源
@override
void dispose() {
_datachannelController.destroy();
DataChannelCallbackManager().unregister(_datachannelController.uuid);
super.dispose();
}
- 是否已连接
Future<void> isConnect() async {
try{
bool result = await _datachannelController.isConnect();
if(result) {
print("已链接");
} else {
print("未链接");
}
}catch(e){
print("isConnect e:${e.toString()}");
}
}
- 发送消息
void sendMessage() async {
busiId += 1;
final timestamp = (DateTime.now().millisecondsSinceEpoch).toString();
final Map<String, dynamic> dic = {
"serviceId": "echo",
"data": {"type": 0, "id": busiId, "data": timestamp},
};
final jsonString = jsonEncode(dic);
final Uint8List data = utf8.encode(jsonString);
final sendData = RTCXFluDCCustomDataTransfer(
busiId: busiId.toString(),
cmdId: 100,
customBinary: data,
);
try{
await _datachannelController.sendMessage(sendData);
}catch(e){
print("sendMessage e:${e.toString()}");
}
}
- 收到消息
// 收到消息通过datachannelController.onData接收消息
_datachannelController.onData.listen((RTCXFluDCCustomDataTransfer event) {
if (event.customBinary != null) {
String content = utf8.decode(event.customBinary!);
setState(() {
contentText = "收到消息: $content";
});
}
});
P2P预连接
在设备列表页,提前建立设备P2P预连接,可加快后续视频播放或dataChannel对P2P通道的复用,提高数据传输速率。
// 引入头文件
import 'package:airtc_engine/src/preconnect/rtcx_preconnect.dart';
class RTCXFluPreConnectApi {
/// 预连接设备
Future<void> startPreconnect(RTCXFluPreconnectReq model)
}
调用示例如下:
final RTCXFluPreConnectApi preConnectApi = RTCXFluPreConnectApi();
try{
RTCXFluPreconnectReq req = RTCXFluPreconnectReq(
iotId: widget.iotId,
productKey: widget.productKey,
deviceName: widget.deviceName,);
preConnectApi.startPreconnect(req);
}catch(e) {
print("startPreconnect e:${e.toString()}");
}