跳到主要内容

音视频播放

更新时间: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()}");
}

倍速播放

注意
  1. 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()}");
}