音视频播放
提供设备的直播、点播、截图、录制等相关功能
播放器的View是 IoTPlayerView ,类似于 MediaPlayer,View 和 Controller分离。
class IoTPlayerView{
/**
* 初始化播放器参数
*/
fun init(IoTPlayerViewInitParam param)
/**
* 是否将当前播放器置顶
*/
fun setZOrderMediaOverlay(isMediaOverlay: Boolean)
}
<com.polaris.iot.appsdk.libplayer.IoTPlayerView
android:id="@+id/iotPlayerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
播放器View初始化
/**
* 初始化,设置解码模式及设备信息
* @param mode视频确码模式,硬解或软解
* @param playDeviceInfo 设备信息信息
*/
class IoTPlayerViewInitParam(VideoDecodeModeEnum mode, IoTDeviceInfo playDeviceInfo)
示例
private DeviceInfo mDeviceInfo; //设备列表获取到的设备信息对象
private IoTDeviceInfo mPlayDeviceInfo = IoTDeviceInfo.copyFrom(mDeviceInfo)
mPlayerView = (IoTPlayerView) findViewById(R.id.iotPlayerView);
//Player初始化
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, mPlayDeviceInfo );
mPlayerView.init(param);
播放器置顶
存在多个播放器且有层级关系,当需要动态调整层级关系时使用
版本要求:1.4.4+
IoTPlayerView playerView = (IoTPlayerView) findViewById(R.id.iotPlayerView);
playerView.setZOrderMediaOverlay(true);
获取IoTPlayerCtrl
IoTPlayerCtrl类负责控制播放器,用于直播、回放、截图等功能
/**
* 获取IoTPlayerCtrl,请在主线程中调用
*/
@MainThread
public IoTPlayerCtrl getPlayerCtrl();
/**
* 播放状态回调
*/
void setOnPlayerListener(IIoTPlayerListener listener)
IIoTPlayerListener
interface IIoTPlayerListener{
/**
* 当播放器的播放状态发生变化,在此方法中回调不同的播放状态
*
* @param playState 播放状态
* @param code 状态码,主要用于状态下
*/
void onPlayStateChanged(IoTPlayerStateEnumplayState playState, int code);
/**
* Player播放过程中交互消息回调
* @param code 消息code
* @param extra 消息额外的值回调
*/
void onPlayMessage(int code, int extra);
/**
* 当视频开始播放时回调视频真实尺寸,需要对{@link IoTPlayerView IoTPlayerView}重新调整大小
*
* @param player
* @param width
* @param height
*/
void onPlayerVideoSize(IoTPlayerInterface player, int width, int height);
}
onPlayStateChanged回调
| playState | 注释 |
|---|---|
| IoTPlayerStateEnum.STOPPED | 播放停止/未开始 |
| IoTPlayerStateEnum.STOPPING | 播放结束中 |
| IoTPlayerStateEnum.ERROR | 播放错误,播放异常后,需要上层控制恢复播放或提示文案 |
| IoTPlayerStateEnum.PREPARING | 播放准备中 |
| IoTPlayerStateEnum.PREPARING_TO_START | 播放准备中,已经调用Start |
| IoTPlayerStateEnum.PREPARED | 播放准备完成 |
| IoTPlayerStateEnum.PREPARED_TO_START | 播放准备就绪,已经调用Start |
| IoTPlayerStateEnum.BUFFERING_START | 缓冲开始 |
| IoTPlayerStateEnum.BUFFERING_END | 缓冲完成 |
| IoTPlayerStateEnum.PLAYING | 播放出图(视频出首个画面) |
| IoTPlayerStateEnum.PAUSED | 暂停播放 |
| IoTPlayerStateEnum.COMPLETED | 播放完成 |
| code | 注释 |
|---|---|
| IoTPlayerErrorCodes.ERROR_CODE_CON_FAILED=100000 | 直播建立连接失败 |
| IoTPlayerErrorCodes.ERROR_CODE_P2P_CON_FAILED=1000001 | P2P建立连接失败 |
| IoTPlayerErrorCodes.ERROR_CODE_P2P_FAILED=1000003 | P2P播放失败 |
| IoTPlayerErrorCodes.ERROR_CODE_P2P_LIMIT=1000004 | P2P播放通道已达设备最大支持数量 |
| IoTPlayerErrorCodes.ERROR_CODE_SDCARD_FAILED= 1000008 | SDCard播放失败(交换sdp失败) |
| IoTPlayerErrorCodes.ERROR_CODE_SDCARD_LIMIT=1000009 | SDCard播放通道已达设备最大支持数量 |
| IoTPlayerErrorCodes.ERROR_CODE_INVALID_RTC_CHANNEL=1000010 | 当前流无效 |
| IoTPlayerErrorCodes.ERROR_CODE_SDP_FAILED=1000011 | 直播连接服务失败(播放设备超时) |
| IoTPlayerErrorCodes.ERROR_CODE_PLAY_DURATION_LIMIT=1000012 | 播放时长限制 |
| IoTPlayerErrorCodes.ERROR_CODE_SDCARD_SDP_TIMEOUT=1000013 | 播放SDCard失败(交换sdp超时) |
| IoTPlayerErrorCodes.ERROR_CODE_SDP_TIMEOUT=1000014 | 直播连接服务失败(交换sdp超时) |
| IoTPlayerErrorCodes.ERROR_CODE_SDCARD_PLAYBACK_END=1000015 | SDCard播放已经没有数据 |
| IoTPlayerErrorCodes.ERROR_CODE_PLAYBACK_URL_FAILED=1000016 | 获取云回看地址失败 |
| IoTPlayerErrorCodes.ERROR_CODE_DEVICE_SLEEPING=1000017 | 低功耗设备正在进入休眠,过程不能中断 |
onPlayMessage 回调
| code | 注释 |
|---|---|
| IoTPlayerMsgEnum.MSG_VIDEO_CODEC_UNSUPPORTED | 不支持硬解(默认),需要转软解 |
初始化示例
import com.polaris.iot.appsdk.libplayer.model.IoTDeviceInfo;
import com.polaris.iot.appsdk.libplayer.enums.VideoDecodeModeEnum;
import com.polaris.iot.appsdk.libuserdevicemgr.model.DeviceInfo;
private DeviceInfo mDeviceInfo; //设备列表获取到的设备信息对象
private IoTDeviceInfo mPlayDeviceInfo = IoTDeviceInfo.copyFrom(mDeviceInfo)
mPlayerView = (IoTPlayerView) findViewById(R.id.iotPlayerView);
//Player初始化
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, mPlayDeviceInfo );
mPlayerView.init(param);
//IoTPlayerCtrl 的初始化
mPlayerCtrl = mPlayerView.getPlayerCtrl();
mPlayerCtrl.setOnPlayerListener(this);
播放相关
通过IoTPlayerView获取到IoTPlayerCtrl后,使用IoTPlayerCtrl来进行播放操作
/**
* 初始化直播
* @param param 播放参数
*/
void prepare(IoTPlayerParam param);
/**
* 初始化回看
* @param param 播放参数
* @param seekParam 回看参数
*/
void prepare(IoTPlayerParam param, IoTPlayerSeekParam seekParam);
/**
* 开始播放
*/
void start();
/**
* seek 到不同位置,如果seek到直播 param = null
* @param param IoTPlayerSeekParam
*/
void seekTo(IoTPlayerSeekParam param);
/**
* 恢复播放
*/
void resume();
/**
* 暂停播放
*/
void pause();
/**
* 停止Player,播放器停止后,必须重新调用prepare
*/
void stop();
/**
* 销毁
*/
void destroy();
直播
IoTPlayerParam param = new IoTPlayerParam();
mPlayerCtrl.prepare(param);
mPlayerCtrl.start()
回放
IoTPlayerParam param = new IoTPlayerParam();
//从数据接口返回
IoTPlayerSeekParam seekParam = new IoTPlayerSeekParam(beginTime,endTime)
mPlayerCtrl.prepare(param,seekParam);
mPlayerCtrl.start();
直播和回放切换
直播和回放互相切换可以无需重新初始化播放器,调用Seek方法即可
//直播
mPlayerCtrl.seekTo(null);
//回看
IoTPlayerSeekParam seekParam = new IoTPlayerSeekParam(beginTime,endTime)
mPlayerCtrl.seekTo(seekParam);
IoTPlayerParam
| 字段 | 类型 | 注释 |
|---|---|---|
| preload | boolean | 是否开启缓存 |
| liveBufferDynamic | boolean | 直播动态buffer |
| liveBufferMaxSize | int | 直播动态buffer最大值 |
| playerViewBgColor | int | 播放器背景颜色 |
| videoCacheSize | int | 回放缓存大小 |
| muted | boolean | 是否静音播放 |
| isPlayMultiLens | boolean | 多目设备是否是多个镜头一起播放 |
停止播放
停止Player,播放器停止后,必须重新调用prepare
请在退出播放界面或播放界面退到后台,停止播放
//停止放器
mPlayerCtrl.stop();
销毁
销毁播放器,销毁后播放器不再可用
mPlayerCtrl.destroy()
mPlayerCtrl = null
软硬解切换
默认硬解,但由于硬件不支持或数据异常等原因硬解失败,需要切成软解
调用前需要先停止播放器
//停止放器
mPlayerCtrl.stop();
//切换软硬解切换
mPlayerCtrl.switchVideoDecodeMode();
//当前解码模式
mPlayerCtrl.getVideoDecodeMode();
边播边录
- 申请文件写入权限
private int accessStorageWritePermission() {
String permission = Build.VERSION.SDK_INT > 32 ? Manifest.permission.READ_MEDIA_VIDEO : Manifest.permission.WRITE_EXTERNAL_STORAGE;
int permissionCheck = ContextCompat.checkSelfPermission(this, permission);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{permission}, REQUEST_PERMISSTION);
}
return permissionCheck;
}
- 开始录制/停止录制
recordStart需要在播放成功后调用
/**
* 开始边播边录
* @param filePath 录制文件存储目录
* @return
*/
boolean recordStart(String filePath);
/**
* 结束边播边录
* @return
*/
boolean recordStop();
- 设置录制回调
请在每次recordStart前设置回调,recordStop时会主动清除设置的listener
/**
* 设置边播边录回调
* @param listener
* @throws IllegalArgumentException
* @throws IllegalStateException
* @throws IOException
*/
public void setOnPOLARISReorderListener(POLARISMediaPlayer.OnPOLARISReorderListener listener) throws IllegalArgumentException, IllegalStateException, IOException;
public interface OnPOLARISReorderListener {
/*
* @param POLARISMediaPlayer: 播放器对象
* @param type:返回类型,POLARISMediaPlayer.POLARIS_MP4_MUX_PROGRESS,POLARISMediaPlayer.POLARIS_MP4_MUX_FILEPATH,POLARISMediaPlayer.POLARIS_MP4_MUX_ERROR
* @param progress:录制时长,单位ms
* @param sFilePath:保存录像文件路径, type == POLARISMediaPlayer.POLARIS_MP4_MUX_FILEPATH 时返回
*/
void onPOLARISReorderCallback(POLARISMediaPlayer POLARISMediaPlayer, int type, int progress, String sFilePath);
}
示例
//开始录制
private boolean startRecord() {
if (accessStorageWritePermission() != PackageManager.PERMISSION_GRANTED) return false;
if (mPlayerCtrl != null) {
try {
mPlayerCtrl.setOnPOLARISReorderListener(new POLARISMediaPlayer.OnPOLARISReorderListener() {
@Overridepublic void onPOLARISReorderCallback(POLARISMediaPlayer POLARISMediaPlayer, int type, int progress, String sFilePath) {
if (type == POLARISMediaPlayer.POLARIS_MP4_MUX_PROGRESS) {
IoTLogger.d(TAG, "onPOLARISReorderCallback progress : " + progress);
} else if (type == POLARISMediaPlayer.POLARIS_MP4_MUX_FILEPATH) {
IoTLogger.d(TAG, "onPOLARISReorderCallback progress : " + progress + " ,FilePath : " + sFilePath);
Toast.makeText(LivePreviewActivity.this, "record success, FilePath = " + sFilePath, Toast.LENGTH_LONG).show();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
mPlayerCtrl.recordStart(DirectoryMgrUtils.getVideoRecorderDir());
isRecording = true;
}
return true;
}
//停止录制
private void stopRecord() {
if (mPlayerCtrl != null) {
mPlayerCtrl.recordStop();
}
isRecording = false;
}
截图
需要在播放成功后调用
调用IoTPlayerCtrl.screenshot()实现,该方法通过回调返回bitmap对象
/**
* 截图
* @return
*/
Bitmap screenshot(listener: IScreenshotListener);
示例
mPlayerCtrl.screenshot(bitmap -> {
if (bitmap != null) {
showScreenShotDialog(bitmap);
}
});
画面旋转
版本要求:1.4.2
设置播放画面旋转角度,需要在播放成功后调用
/**
* 设置播放画面旋转角度
* @param rotation 角度,仅支持:0、90、180、270
* @return
*/
fun setVideoRotation(rotation: Int)
示例
mPlayerCtrl.setVideoRotation(180);
静音
调用IoTPlayerCtrl.setVolume(float volume)实现
/**
* 截图
* @param volume 0:静音 1:取消静音
* @return
*/
setVolume(float volume);
示例
mPlayerCtrl.setVolume(0);
设置回看速率
调用IoTPlayerCtrl.setPlaybackRate(float rate)实现
存储卡回看播放在非 1 倍速播放时没有声音、不能边播边录及截图
/**
* 设置回放速率
*
* @param rate
* @return 设置是否成功
*/
fun setPlaybackRate(rate: Float): Boolean
示例
private final float[] mPlaybackSpeedSupport = {0.5f, 1.0f 2.0f, 4.0f};
int playbackSpeed= mPlaybackSpeedSupport[2]
mPlayerCtrl.setPlaybackRate(playbackSpeed);
获取回看速率
调用IoTPlayerCtrl.getPlaybackRate()实现
/**
* 设置回放速率
*
* @param rate
* @return 设置是否成功
*/
fun getPlaybackRate(): Float
示例
mPlayerCtrl.getPlaybackRate();
播放主子码流
- 初始化指定码流
默认主码流 0
//切换码流
int MAIN_STREAM = 0;
int SUB_STREAM_1 = 1;
//其它 2 3 4 5
mPlayerView = (IoTPlayerView) findViewById(R.id.iotPlayerView);
//Player初始化
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, mPlayDeviceInfo);
//指定子码流
params.setStreamChannelId(SUB_STREAM_1);
mPlayerView.init(param);
- 中间切换
调用前需要先停止播放器
int MAIN_STREAM = 0;
int SUB_STREAM_1 = 1;
//其它 2 3 4 5
//停止播放器
mPlayerCtrl.stop();
//切换码流
mPlayerCtrl.switchStreamChannel(SUB_STREAM_1);
获取播放时间戳
获取当前播放绝对时间戳,可能为0
long timestamp = mPlayerCtrl.getCurrentAbsoluteTime();
直播码率
获取直播时码率 单位: bps
long bitrate = mPlayerCtrl.getBitrate();
直播帧率
获取直播时帧率 单位: fps
long fps = mPlayerCtrl.getPlayFPS();
画面缩放倍数
获取画面当前放大倍数。范围:[1.0, 4.0]
float rate = mPlayerCtrl.getScaleRation();
直播预连接
使用场景:设备列表展示界面,当设备处于列表可见区域时调用,用于优化直播首屏效果
设备工作模式为4的低功耗设备不需要使用预连接功能
工作模板4:低功耗设备,iotsdk和streamsdk运行在主控芯片上,wifi或4g芯片上仅维持简单心跳保活,支持远程唤醒
package com.polaris.iot.appsdk.libplayer
/**
* 设备直播预连接管理
*/
object IoTPlayPreConnectMgr{
/**
* 通知设备设备与服务预连接
*
* @param deviceInfo
*/
@JvmStatic
fun notifyDevicePreConnect(deviceInfo: DeviceInfo)
/**
* 播放预先建立播放通道
*
* @param context
* @param deviceInfo
*/
@JvmStatic
fun preConnect(context: Context, deviceInfo: DeviceInfo)
}
示例:
rvDeviceList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
doVisibleDeviceExtraAction();
}
super.onScrollStateChanged(recyclerView, newState);
}
});
private void doVisibleDeviceExtraAction() {
LinearLayoutManager layoutManager = (LinearLayoutManager) binding.rvDeviceList.getLayoutManager();
if (layoutManager == null) return;
int first = layoutManager.findFirstVisibleItemPosition();
int last = layoutManager.findLastVisibleItemPosition();
IoTLogger.d(TAG, "player preload first=" + first + ",last=" + last);
for (int i = first; i <= last; i++) {
IoTLogger.d(TAG, "player preload i=" + i);
DeviceInfo deviceInfo = mDeviceListAdapter.getItem(i);
//播放预建立通道 可选
IoTPlayPreConnectMgr.preConnect(requireContext(), deviceInfo);
//通知设备与服务建立直播预连接 可选
IoTPlayPreConnectMgr.notifyDevicePreConnect(deviceInfo);
}
}
云视频下载
使用场景:下载云端录制视频( 最大5分钟)
请先申请文件写入权限
package com.polaris.iot.appsdk.libplayer
/**
* 云录制视频下载
*
* @property iotDeviceInfo 视频信息
* @property fileOption 文件选项
* @constructor
*
* @param downloaderListener 下载状态监听
*/
class IoTPlaybackDownloader(
private val deviceInfo: DeviceInfo,
private val fileOption: IoTPlaybackDownloadFileOption,
downloaderListener: IoTPlaybackDownloadListener
){
/**
* 开始下载 最大5分钟
*
* @param beginTime
* @param endTime
*/
fun start(beginTime: Long, endTime: Long)
/**
* 双目设备开始下载 最大5分钟
*
* @param beginTime 开始时间
* @param endTime 结束时间
* @param lensId 设备镜头id
*/
fun start(beginTime: Long, endTime: Long, lensId: Int?)
/**
* 停止下载
*
* @param beginTime
* @param endTime
*/
fun stop()
}
下载文件选项
package com.polaris.iot.appsdk.libplayer.model
/**
* 云录制下载文件配置选项
*
* @property saveDirection 下载文件保存目录
* @property fileBasename 下载文件名称前缀
* @property maxFileDuration 下载文件总时长(单位:秒)
* @constructor Create empty Io t stream download file option
*/
data class IoTPlaybackDownloadFileOption(
val saveDirection: String,
var fileBasename: String?,
var maxFileDuration: Float?
) {
constructor(saveDirection: String) : this(saveDirection, null, null)
}
下载回调
interface IoTPlaybackDownloadListener {
/**
* 录制过程
*
* @param progress 录制时长
* @param completed 是否完成
*/
fun onProgress(progress: Long, completed: Boolean)
/**
* 录制文件保存地址
*
* @param filePath
*/
fun onFilePath(filePath: String?)
/**
* 异常回调
*
* @param code
*/
fun onError(code: Int)
}
下载监听onError回调code值
/**
* 下载监听onError回调code值
*
*/
object IoTPlaybackDownloadCodeEnum {
const val POLARIS_MUXER_SUCCESS = 0
/**
* 回看下载地址失败
*/
const val POLARIS_MUXER_EURL = 60601
/**
* 初始化失败
*/
const val POLARIS_MUXER_EINIT = 60602
const val POLARIS_MUXER_ENOTINIT = 60603
const val POLARIS_MUXER_EPERM = 60604
/**
* 调用ffmpeg失败——非法参数
*/
const val POLARIS_MUXER_EINVAL = 60605
const val POLARIS_MUXER_EUNINIT = 60606
/**
* 使用ffmpeg失败——初始化filter失败
*/
const val POLARIS_MUXER_EFILTER = 60607
/**
* 字库路径非法
*/
const val POLARIS_MUXER_EFONTPATH = 60608
/**
* logo路径非法
*/
const val POLARIS_MUXER_ELOGOPATH = 60609
/**
* 输出文件打开失败
*/
const val POLARIS_MUXER_EOUTPUTFILE = 60610
/**
* 解码失败
*/
const val POLARIS_MUXER_EVIDEOFRAME = 60611
}
下载示例:
import com.polaris.iot.appsdk.libplayer.IoTPlaybackDownloader;
private IoTPlaybackDownloader mPlaybackDownloader = null;
private String directoryPath = "录制文件保存目录"
private IoTDeviceInfo mPlayDeviceInfo = IoTDeviceInfo.copyFrom(deviceInfo)
private void startDownload() {
if (mPlaybackDownloader == null) {
IoTPlaybackDownloadFileOption fileOption = new IoTPlaybackDownloadFileOption(directoryPath);
mPlaybackDownloader = new IoTPlaybackDownloader(mPlayDeviceInfo, fileOption, new IoTPlaybackDownloadListener() {
@Override
public void onProgress(long progress, boolean completed) {
IoTLogger.d(TAG, "onProgress progress=" + progress + ", completed=" + completed);
}
@Override
public void onFilePath(@Nullable String filePath) {
hideLoading();
if (mPlaybackDownloader != null) mPlaybackDownloader.stop();
IoTLogger.i(TAG, "onFilePath filePath=" + filePath);
showToast(getString(R.string.download_success) + ":" + filePath);
}
@Override
public void onError(int code) {
hideLoading();
if (mPlaybackDownloader != null) mPlaybackDownloader.stop();
IoTLogger.e(TAG, "onError code=" + code);
switch (code) {
case IoTPlaybackDownloadCodeEnum.POLARIS_MUXER_EINIT:
showToast(R.string.error_muxer_init);
break;
case IoTPlaybackDownloadCodeEnum.POLARIS_MUXER_EUNINIT:
showToast(R.string.error_muxer_uninit);
break;
case IoTPlaybackDownloadCodeEnum.POLARIS_MUXER_EINVAL:
showToast(R.string.error_muxer_params);
break;
case IoTPlaybackDownloadCodeEnum.POLARIS_MUXER_EFILTER:
showToast(R.string.error_muxer_filter);
break;
case IoTPlaybackDownloadCodeEnum.POLARIS_MUXER_EOUTPUTFILE:
showToast(R.string.error_output_file);
break;
}
}
});
}
IoTLogger.d(TAG, "start download startTime=" + mSeekParam.getBeginTime() + ", endTime=" + mSeekParam.getEndTime());
mPlaybackDownloader.start(mSeekParam.getBeginTime(), mSeekParam.getEndTime());
//多目设备下载指定镜头录像需要传lensId
// int lensId = 0
//mPlaybackDownloader.start(mSeekParam.getBeginTime(), mSeekParam.getEndTime(), lensId);
}
播放示例
提供播放器使用示例
退到后台,请调用stop,回到前台重新prepare 再 start
基础示例
以在Fragment为例,在Activity中接入播放器同理,注意在各生命周期对播放接口的使用
退出播放界面时,请停止播放
package com.polaris.iot.appsdk.xsdemoapp.ui.testplay;
import static androidx.navigation.Navigation.findNavController;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.polaris.iot.appsdk.libcommon.log.IoTLogger;
import com.polaris.iot.appsdk.libplayer.IoTPlayerCtrl;
import com.polaris.iot.appsdk.libplayer.IoTPlayerInterface;
import com.polaris.iot.appsdk.libplayer.IoTPlayerParam;
import com.polaris.iot.appsdk.libplayer.enums.IoTPlayerMsgEnum;
import com.polaris.iot.appsdk.libplayer.enums.IoTPlayerStateEnum;
import com.polaris.iot.appsdk.libplayer.enums.IoTStreamTypeEnum;
import com.polaris.iot.appsdk.libplayer.enums.VideoDecodeModeEnum;
import com.polaris.iot.appsdk.libplayer.hook.IIoTPlayerListener;
import com.polaris.iot.appsdk.libplayer.model.IoTDeviceInfo;
import com.polaris.iot.appsdk.libplayer.model.IoTPlayerViewInitParam;
import com.polaris.iot.appsdk.libuserdevicemgr.model.DeviceInfo;
import com.polaris.iot.appsdk.xsdemoapp.R;
import com.polaris.iot.appsdk.xsdemoapp.databinding.FragmentTestPlayBinding;
public class PlayerTestFragment extends Fragment implements IIoTPlayerListener {
private static final String TAG = "PlayerTestFragment";
public static PlayerTestFragment newInstance(DeviceInfo deviceInfo) {
Bundle args = new Bundle();
args.putSerializable("deviceInfo", deviceInfo);
PlayerTestFragment fragment = new PlayerTestFragment();
fragment.setArguments(args);
return fragment;
}
private FragmentTestPlayBinding binding;
private DeviceInfo mDeviceInfo;
private IoTDeviceInfo iotDevice;
private IoTPlayerCtrl playerCtrl;
private DisplayMetrics mDisplayMetrics;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
IoTLogger.d(TAG, "onCreateView");
binding = FragmentTestPlayBinding.inflate(inflater, container, false);
Bundle bundle = getArguments();
if (bundle == null) {
Intent intent = requireActivity().getIntent();
if (intent != null) bundle = intent.getExtras();
}
if (bundle != null) {
mDeviceInfo = (DeviceInfo) bundle.getSerializable("deviceInfo");
}
mDisplayMetrics = new DisplayMetrics();
requireActivity().getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
initView();
return binding.getRoot();
}
@Override
public void onDestroyView() {
super.onDestroyView();
IoTLogger.d(TAG, "onDestroyView");
iotDevice = null;
playerCtrl = null;
}
@Override
public void onStart() {
super.onStart();
IoTLogger.d(TAG, "onStart initPlayer");
initPlayer();
}
private void initView() {
binding.llTitleBarWrapper.tvTitleBarTitle.setText("播放Fragment");
binding.llTitleBarWrapper.ibTitleBarLeft.setOnClickListener(v -> {
requireActivity().onBackPressed();
});
ImageButton ibTitleBarRight = binding.llTitleBarWrapper.ibTitleBarRight;
ibTitleBarRight.setVisibility(View.VISIBLE);
ibTitleBarRight.setOnClickListener(v -> {
findNavController(v).navigate(R.id.test_setting_frag, null);
});
binding.btnToLandscape.setOnClickListener(v -> {
showLandscape(true);
});
binding.btnToPortal.setOnClickListener(v -> {
showLandscape(false);
});
}
@Override
public void onResume() {
super.onResume();
IoTLogger.d(TAG, "onResume");
if (playerCtrl != null) playerCtrl.resume();
}
@Override
public void onPause() {
super.onPause();
IoTLogger.d(TAG, "onPause");
if (playerCtrl != null) playerCtrl.pause();
}
@Override
public void onStop() {
super.onStop();
IoTLogger.d(TAG, "onStop");
if (playerCtrl != null) playerCtrl.stop();
}
private void initPlayer() {
if (mDeviceInfo == null) throw new IllegalStateException("need device info");
if (playerCtrl == null) {
// val deviceInfo = DeviceInfo("","","")
iotDevice = IoTDeviceInfo.copyFrom(mDeviceInfo);
IoTLogger.d(TAG, "init iotDevice=" + (iotDevice));
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, iotDevice);
//param.setStreamChannelId(0);
//IoTPlayerCtrl 的初始化
binding.iotPlayerView.init(param);
playerCtrl = binding.iotPlayerView.getPlayerCtrl();
playerCtrl.setOnPlayerListener(this);
IoTLogger.d(TAG, "init ");
}
playerCtrl.prepare(new IoTPlayerParam());
playerCtrl.start();
}
@Override
public void onPlayStateChanged(@NonNull IoTPlayerStateEnum playState, @Nullable Integer code) {
IoTLogger.d(TAG, "onPlayMessage code=" + code + " playState=" + playState);
}
@Override
public void onPlayMessage(@NonNull IoTPlayerMsgEnum code, int extra) {
IoTLogger.d(TAG, "onPlayMessage code=" + code + " extra=" + extra);
}
@Override
public void onPlayerVideoSize(@Nullable IoTPlayerInterface player, int width, int height) {
IoTLogger.d(TAG, "onPlayMessage width=" + width + " height=" + height);
//根据播放视频的宽高比例,调整播放窗口,避免画面变形
FrameLayout playerViewWrapper = binding.playerViewWrapper;
ViewGroup.LayoutParams layoutParams = playerViewWrapper.getLayoutParams();
layoutParams.width = mDisplayMetrics.widthPixels;
layoutParams.height = Math.min(mDisplayMetrics.widthPixels * height / width, mDisplayMetrics.widthPixels * 9 / 16);
playerViewWrapper.setLayoutParams(layoutParams);
}
public void showLandscape(boolean isLandscape) {
requireActivity().setRequestedOrientation(
isLandscape ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
: ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
ViewGroup.LayoutParams layoutParams = binding.playerViewWrapper.getLayoutParams();
if (isLandscape) {
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
binding.llTitleBarWrapper.llTitleBarWrapper.setVisibility(View.GONE);
binding.btnToPortal.setVisibility(View.VISIBLE);
} else {
layoutParams.width = mDisplayMetrics.widthPixels;
int videoWidth = playerCtrl.getVideoWidth();
int videoHeight = playerCtrl.getVideoHeight();
IoTLogger.d(TAG, "onConfigurationChanged videoWidth=" + videoWidth + ",videoHeight=" + videoHeight);
if (videoWidth > 0 && videoHeight > 0) {
layoutParams.height = (mDisplayMetrics.widthPixels) * videoHeight / videoWidth;
} else {
layoutParams.height = (mDisplayMetrics.widthPixels) * 9 / 16;
}
binding.llTitleBarWrapper.llTitleBarWrapper.setVisibility(View.VISIBLE);
binding.btnToPortal.setVisibility(View.GONE);
}
binding.flexboxLayout.setVisibility(isLandscape ? View.GONE : View.VISIBLE);
binding.playerViewWrapper.setLayoutParams(layoutParams);
super.onConfigurationChanged(newConfig);
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<FrameLayout
android:id="@+id/playerViewWrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="h,16:9"
app:layout_constraintTop_toBottomOf="@id/llTitleBarWrapper">
<com.polaris.iot.appsdk.libplayer.IoTPlayerView
android:id="@+id/iotPlayerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btnToPortal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:text="竖屏" />
</FrameLayout>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/flexboxLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:paddingHorizontal="16dp"
app:alignItems="stretch"
app:flexWrap="wrap"
app:justifyContent="space_evenly"
app:layout_constraintTop_toBottomOf="@id/playerViewWrapper">
<Button
android:id="@+id/btnToLandscape"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="横屏" />
</com.google.android.flexbox.FlexboxLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
多目设备播放
请先查阅 【播放示例-基础示例】
- 多目设备播放接入需要创建多个播放器
- 初始化playerView时,需要不同IoTDeviceInfo实例,并设置lensId
- 截图、录制、静音等操作需要同时操作两个播放器(根据实际业务情况来定)
示例
import com.polaris.iot.appsdk.libplayer.model.IoTDeviceInfo;
import com.polaris.iot.appsdk.libplayer.enums.VideoDecodeModeEnum;
import com.polaris.iot.appsdk.libuserdevicemgr.model.DeviceInfo;
//列表接口获取
private DeviceInfo mDeviceInfo;
IoTDeviceInfo mPlayDeviceInfo = IoTDeviceInfo.copyFrom(mDeviceInfo);
//多目设备 非多目设备,不需要设置lensId参数
if(mDeviceInfo.lensCount>1)mPlayDeviceInfo.lensId = 0
//Player初始化
mPlayerView = (IoTPlayerView) findViewById(R.id.iotPlayerView);
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, mPlayDeviceInfo );
//如果同时播放多个镜头设置,单个不用设置该参数
param.setPlayMultiLens(true); //kotlin param.isPlayMultiLens = true
mPlayerView.init(param);
//IoTPlayerCtrl 的初始化
mPlayerCtrl = mPlayerView.getPlayerCtrl();
IIoTPlayerListener playerListener1 = new IIoTPlayerListener()
mPlayerCtrl.setOnPlayerListener(playerListener1);
//初始化第二个播放器
if(mDeviceInfo.lensCount>1){
IoTDeviceInfo mPlayDeviceInfo2 = IoTDeviceInfo.copyFrom(mDeviceInfo);
mPlayDeviceInfo2.lensId = 1
//Player初始化
mPlayerView2 = (IoTPlayerView) findViewById(R.id.iotPlayerView2);
IoTPlayerViewInitParam param2 = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, mPlayDeviceInfo2 );
//如果同时播放多个镜头设置,单个不用设置该参数
param2.setPlayMultiLens(true); //kotlin param2.isPlayMultiLens = true
mPlayerView2.init(param2);
//IoTPlayerCtrl 的初始化
mPlayerCtrl2 = mPlayerView.getPlayerCtrl();
IIoTPlayerListener playerListener2 = new IIoTPlayerListener()
mPlayerCtrl2.setOnPlayerListener(playerListener2);
}
低功耗设备播放
请先查阅 【播放示例-基础示例】
低功耗设备由于实现方案的不同,播放的方式有所区别,以下是设备DeviceSDK中定义的几种方式
/**
* XS_IOT_WORK_MODE_LOWPOWER_PROXY = 1, //低功耗设备,代理模式,注意只有支持低功耗保活连接的设备才需要设置此模式,设置了此模式之后必须和wifi 芯片上的 lowPwrSDK配合才能工作
* XS_IOT_WORK_MODE_LONG_TIME_POWER = 2 , //长上电设备,正常链接模式(iotsdk和streamsdk运行在一个进程中)
* XS_IOT_WORK_MODE_LOWPOWER_SEPARATE = 3, //低功耗设备,iotsdk和streamsdk分离模式,iotsdk运行在wifi或4g芯片上维持信令长连接,streamsdk运行在主控芯片上
* XS_IOT_WORK_MODE_LOWPOWER_KEEPLIVE = 4, //低功耗设备,iotsdk和streamsdk运行在主控芯片上,wifi或4g芯片上仅维持简单心跳保活,支持远程唤醒
*/
工作模式为1,3的低功耗设备低功耗设备与常长电设备(工作模式为2)播放流程一致,以下示例仅为工作模式4的特殊播放流程
- 先唤醒设备(即使设备列表获取状态为上线,也需要唤醒一下设备)
- 监听设备上线
- 调用播放接口 (请务必在设备上线后调用)
package com.polaris.iot.appsdk.libuserdevicemgr
/**
* 低功耗设备唤醒
*
* @property mIotId 设备唯一ID
* @property mWaitTimeout 超时时长 单位:毫秒 默认30_000
* @property mRefreshInterval 轮询频率 单位:毫秒 默认1000
* @constructor Create empty Device wake up mgr
*/
class DeviceWakeUpMgr(
private val mIotId: String,
private var mWaitTimeout: Long,
private var mRefreshInterval: Long
){
constructor(iotId: String)
constructor(iotId: String, waitTimeout: Long)
/**
* 唤醒设备
*
*/
fun wakeup()
/**
* 结束唤醒
*
*/
fun stop()
/**
* 设备状态监听
*
* @param listener
*/
fun setOnStatusListener(listener: OnResultListener<DeviceStatus>)
}
示例
import com.polaris.iot.appsdk.libuserdevicemgr.DeviceWakeUpMgr;
import com.polaris.iot.appsdk.libplayer.model.IoTDeviceInfo;
import com.polaris.iot.appsdk.libplayer.enums.VideoDecodeModeEnum;
import com.polaris.iot.appsdk.libuserdevicemgr.model.DeviceInfo;
//列表接口获取
private DeviceInfo mDeviceInfo;
private DeviceWakeUpMgr mDeviceWakeupMgr;
@Override
public void onStart() {
super.onStart();
IoTDeviceInfo mPlayDeviceInfo = IoTDeviceInfo.copyFrom(mDeviceInfo);
//Player初始化
mPlayerView = (IoTPlayerView) findViewById(R.id.iotPlayerView);
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, mPlayDeviceInfo );
mPlayerView.init(param);
//IoTPlayerCtrl 的初始化
mPlayerCtrl = mPlayerView.getPlayerCtrl();
IIoTPlayerListener playerListener1 = new IIoTPlayerListener()
mPlayerCtrl.setOnPlayerListener(playerListener1);
}
@Override
public void onResume() {
super.onResume();
//直播唤醒
if ("4".equals(mDeviceInfo.getDeviceWorkMode()) && mPlayDeviceInfo.isOnline()) {
if (mDeviceWakeupMgr == null) {
mDeviceWakeupMgr = new DeviceWakeUpMgr(mDeviceInfo.getIotId());
//监听设备状态,设备上线后开始直播
//也可以在【物模型功能->订阅设备事件】中订阅设备上下线状态
mDeviceWakeupMgr.setOnStatusListener(new OnResultListener<DeviceStatus>() {
@Override
public void onResult(DeviceStatus result) {
//更新设备状态
mDeviceInfo.setStatus(result.getStatus());
mPlayDeviceInfo = IoTDeviceInfo.copyFrom(mDeviceInfo);
IoTPlayerParam param = new IoTPlayerParam();
mPlayerCtrl.prepare(param);
mPlayerCtrl.start();
}
@Override
public void onError(@NonNull IoTSDKError error) {
}
});
}
//开始唤醒设备
mDeviceWakeupMgr.wakeup();
showPlayerLoading();
return;
}
}
@Override
public void onPause() {
//结束唤醒设备
if (mDeviceWakeupMgr != null) mDeviceWakeupMgr.stop();
}
全景图
设备按指定角度依次旋转并截图拼接成一张全景图。用户可点击全景图某区域控制设备转向该区域
不再使用时请销毁实例
package com.polaris.iot.appsdk.libplayer.datachannel
/**
* 全景拼接
*
* @property context 上下文件
* @property deviceInfo 设备信息[IoTDeviceInfo]
* @property deviceMoveAngle 转动角度 默认30度
* @property deviceAngleInfo 设备PTZ角度信息[DeviceAngleInfo]
* @constructor Create empty Io t media pusher
*/
class PanoramicStitching(
context: Context,
deviceInfo: IoTDeviceInfo,
private var deviceMoveAngle: Int,
private var deviceAngleInfo: DeviceAngleInfo?
) {
constructor(context: Context, deviceInfo: IoTDeviceInfo) : this(context, deviceInfo, PanoramicStitching.CAPTURE_MOVE_ANGLE, null)
/**
* 开始全景拼接 需要设备能力支持
* @param listener 回调拼接后的图片[OnResultListener]<[Bitmap]>
*
*/
fun startStitching(listener: OnResultListener<Bitmap>)
/**
* 开始全景拼接 需要设备能力支持
* @param listener 回调拼接后的图片[OnResultListener]<[Bitmap]>
* @param timeoutMs 超时,默认1分钟
*
*/
fun startStitching(listener: OnResultListener<Bitmap>, timeoutMs: Long)
/**
* 停止
*
*/
fun stopStitching()
/**
* 销毁
*
*/
fun destroy()
/**
* 获取上次生成的全景拼接图片
*
* @return
*/
fun getStitchingImage(): Bitmap?
/**
* 是否支持全景拼接
*
* @return
*/
fun isSupport(): Boolean
/**
* 获取全景水平角度
* @param xOffset 全景图水平偏移位置
* @return 取值范围 [-1,1]
*/
fun getAngleHorizontal(xOffset: Int): Float?
/**
* 全景图宽度 全景图生成成功或有缓存的图片时有值
*/
val panoramicImageWidth: Int
/**
* 全景图高度 全景图生成成功或有缓存的图片时有值
*/
val panoramicImageHeight: Int
}
设备云台角度信息,可从物模型获取
/**
* Device angle info
*
* @property panAngle 水平可转动角度
* @property hfov 水平视场角
* @property vfov 垂直视场轴角
*/
data class DeviceAngleInfo(
/**
* 水平可转动角度
*/
val panAngle: Int,
/**
* 水平视场角
*/
val hfov: Int,
/**
* 垂直视场角
*/
val vfov: Int,
)
示例
生成全景图
private PanoramicStitching mPanoramicStitching;
//创建
private void createPanoramicStitching() {
if (mPanoramicStitching == null) {
mPanoramicStitching = new PanoramicStitching(LivePlayActivity.this, mPlayDeviceInfo);
}
}
//销毁
private void destroyPanoramicStitching() {
if (mPanoramicStitching != null) {
mPanoramicStitching.destroy();
mPanoramicStitching = null;
}
}
//显示全景图
private void showPicStitchingView() {
ConstraintLayout clPicStitching = findViewById(R.id.clPicStitching);
clPicStitching.setVisibility(View.VISIBLE);
createPanoramicStitching();
if (mPanoramicStitching != null) {
//获取缓存的全景图显示在ImageView
Bitmap bitmap = mPanoramicStitching.getStitchingImage();
if (bitmap != null) {
ImageView ivPicStitching = findViewById(R.id.ivPicStitching);
ivPicStitching.setImageBitmap(bitmap);
}
}
}
//获取全景图
private void startPanoramicStitching() {
showLoading();
createPanoramicStitching();
mPanoramicStitching.startStitching(new OnResultListener<Bitmap>() {
@Override
public void onResult(Bitmap result) {
ImageView ivPicStitching = findViewById(R.id.ivPicStitching);
ivPicStitching.setImageBitmap(result);
hideLoading();
}
@Override
public void onError(@NonNull IoTSDKError error) {
hideLoading();
IoTLogger.e(TAG, "dataChannel startPanoramicStitching error", error);
showToast(R.string.tip_operate_failed);
}
});
}
@Override
public void onDestroy() {
destroyPanoramicStitching()
}
点击全景图控制设备转动
findViewById(R.id.ivPicStitching).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mPanoramicStitching == null) return false;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return true;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
int viewWidth = v.getWidth();
int imgWidth = mPanoramicStitching.getPanoramicImageWidth();
float xOffset = event.getX() / viewWidth * imgWidth;
Float angle = mPanoramicStitching.getAngleHorizontal((int) xOffset);
if (angle != null) {
Map<String, Integer> params = Map.of(
"X", (int) (10000 * angle),
"Y", 0
);
IoTLogger.d(TAG, "dataChannel PanoramicStitching ex=" + event.getX()
+ ",viewW=" + viewWidth + ",imgW=" + imgWidth + ",imgOffset=" + xOffset
+ ",angle=" + angle + ",params=" + JSONObject.toJSONString(params));
mPanelDevice.invokeService(ThingInvokeServiceType.PTZ_PANORAMIC_MOVE.getIdentifier(), params);
}
}
return false;
}
});
获取音视频编码数据
播放器支持将未解码的音视频编码数据回调给App,用于上层自定义渲染画面、追踪特写等相关业务,当回调编码数据时,播放器将不再渲染播放视频画面。如果需要回调编码数据,请在初始化播放器对象时,设置必要入参。
设置参数
IoTPlayerViewInitParam param = new IoTPlayerViewInitParam(VideoDecodeModeEnum.HARDWARE, iotDevice);
//启用外部解码渲染,播放器启用透传数据
boolean useExternalRenderer = true
param.setExternalRenderer(useExternalRenderer);
binding.iotPlayerView.init(param);
设置回调
/**
* 功能:外部渲染视频编码数据
*/
interface OnEncodedDataListener {
fun onVideoEncodedData(codecType: VideoEncodedTypeEnum, timestamp: Long, utcTime: Long, data: ByteArray, size: Int)
fun onAudioEncodedData(codecType: AudioEncodedTypeEnum, sampleRate: Int, channelNum: Int, timestamp: Long, data: ByteArray, size: Int)
fun onCustomData(data: ByteArray, size: Int)
}
interface IoTPlayerCtrl{
/**
* 监听编码数据
*
* @param listener
*/
fun setOnEncodedDataListener(listener: OnEncodedDataListener)
}
示例
IoTPlayerCtrl playerCtrl = binding.iotPlayerView.getPlayerCtrl();
playerCtrl.setOnEncodedDataListener(new OnEncodedDataListener() {
@Override
public void onCustomData(@NonNull byte[] data, int size) {
if (size <= 0) return;
IoTLogger.d(TAG, "call onCustomData data.length=" + size);
}
@Override
public void onAudioEncodedData(@NonNull AudioEncodedTypeEnum codecType, int sampleRate, int channelNum, long timestamp, @NonNull byte[] data, int size) {
if (size <= 0) return;
IoTLogger.d(TAG, "call onAudioEncodedData codecType=" + codecType + ", timestamp=" + timestamp + ",data.length=" + size);
}
@Override
public void onVideoEncodedData(@NonNull VideoEncodedTypeEnum codecType, long timestamp, long utcTime, @NonNull byte[] data, int size) {
if (size <= 0) return;
IoTLogger.d(TAG, "call onVideoEncodedData codecType=" + codecType + ", timestamp=" + timestamp + ", utcTime=" + utcTime + ",data.length=" + size);
}
});
DataChannel通道
通过DataChannel发送自定义数据
设置参数
class OperateByDataChannel(
private val context: Context,
protected val deviceInfo: IoTDeviceInfo,
private val sendControlInternal: Long
) {
/**
* 发送自定义数据
*
* @param cmdId 指令,取值需要在1到1000
* @param data 二进制数据 最大128KB
*/
fun sendCmdData(cmdId: Int, data: ByteArray)
/**
* 支持
*/
fun isSupport(): Boolean
/**
* 通道是否可用 true:可用 false:不可用
*/
fun isValidChannel(): Boolean
/**
* 销毁
*/
fun destroy()
/**
* 设置监听
*
* @param onDataChannelListener
*/
fun setOnDataChannelListener(onDataChannelListener: IoTOnDataChannelListener?)
}
/**
* DataChannel 监听
*/
interface IoTOnDataChannelListener {
/**
* 状态监听
*
* @param status 状态
* @param code 扩展状态码
*/
fun onStatusChange(status: DataChannelStateEnum, code: Int)
/**
* 接收远端发送的数据
*
* @param cmdId 指令:取值 1到1000
* @param body 接收的二进制数据
*/
fun onData(cmdId: Int, body: ByteArray)
/**
* 发送缓冲区剩余数据 (暂无用途)
*
* @param previousAmount
*/
fun onDataBuffered(previousAmount: Long)
}
示例
private Timer dataChannelSendTimer;
private OperateByDataChannel mCustomDataChannel;
private void stopDataChannelSendTimer() {
if (dataChannelSendTimer != null) {
dataChannelSendTimer.cancel();
dataChannelSendTimer = null;
}
}
private void startDataChannelSendTimer() {
stopDataChannelSendTimer();
byte[] mp4Bytes = AssetFileReader.readFileFromAssets(this, "media/video_h265.mp4");
if (mp4Bytes == null) {
showLoading("read mp4 failed");
return;
}
int stepSize = 128 * 1024;
int max = mp4Bytes.length;
for (int offset = 0; offset < max; ) {
int size = stepSize;
int nextOffset = offset + size;
if (nextOffset > max) {
size = max - offset;
}
boolean isComplete = nextOffset >= max;
byte[] sendBytes = new byte[size];
System.arraycopy(mp4Bytes, offset, sendBytes, 0, size);
mCustomDataChannel.sendCmdData(isComplete ? 101 : 100, sendBytes);
offset = nextOffset;
}
}
private void destroyCustomDataChannel() {
if (mCustomDataChannel != null) {
mCustomDataChannel.destroy();
mCustomDataChannel = null;
}
stopDataChannelSendTimer();
closeDataChannelFile();
}
private FileSaver mDataChannelFileSaver;
private void closeDataChannelFile() {
if (mDataChannelFileSaver != null) {
mDataChannelFileSaver.close();
mDataChannelFileSaver = null;
}
}
private void createCustomDataChannel() {
if (mCustomDataChannel != null && !mCustomDataChannel.isValidChannel()) {
destroyCustomDataChannel();
}
if (mCustomDataChannel == null) {
mCustomDataChannel = new OperateByDataChannel(this, IoTDeviceInfo.copyFrom(mDeviceInfo));
mCustomDataChannel.setOnDataChannelListener(new IoTOnDataChannelListener() {
@Override
public void onData(int cmdId, @NonNull byte[] body) {
IoTLogger.d(TAG, "onData cmdId =" + cmdId + ", size=" + body.length);
if (cmdId == 100 || cmdId == 101) {
if (mDataChannelFileSaver == null) {
String path = DirectoryMgrUtils.getVideoRecorderDir() + File.separator + mDeviceInfo.getDeviceName() + "_DataChanel_" + System.currentTimeMillis();
mDataChannelFileSaver = new FileSaver(path);
}
mDataChannelFileSaver.append(body);
if (cmdId == 101) {
closeDataChannelFile();
}
}
}
@Override
public void onDataBuffered(long previous) {
IoTLogger.d(TAG, "onDataBuffered " + previous);
}
@Override
public void onStatusChange(@NonNull DataChannelStateEnum status, int code) {
switch (status) {
case STATE_SUCCESS: {
if (mCustomDataChannel != null) {
startDataChannelSendTimer();
}
break;
}
case STATE_ERROR:
destroyCustomDataChannel();
runOnUiThread(() -> {
if (mTestDataChannelDialog != null) {
mTestDataChannelDialog.dismiss();
mTestDataChannelDialog = null;
}
showLongToast(R.string.error_dataChannel);
});
break;
}
}
});
}
}