HarmonyOS鸿蒙Next中求助,求一个播放rtmp://的案例
HarmonyOS鸿蒙Next中求助,求一个播放rtmp://的案例
需要一个rtmp的案例,开箱即用那种,感谢
【解决方案】
HarmonyOS提供了@ohos/ijkplayer三方库播放rtmp视频,具体可以参考ijkplayer(该链接为gitcodede)三方库的使用。
可以参考如下代码,也可以参考ijkplayer的demo(该链接为gitcodede):
// IjkVideoPlayerPage.ets
import { Callback } from '[@ohos](/user/ohos).base';
import { IjkMediaPlayer, InterruptEvent, InterruptHintType, DeviceChangeReason } from '[@ohos](/user/ohos)/ijkplayer';
import { OnPreparedListener } from '[@ohos](/user/ohos)/ijkplayer';
import { OnVideoSizeChangedListener } from '[@ohos](/user/ohos)/ijkplayer';
import { OnCompletionListener } from '[@ohos](/user/ohos)/ijkplayer';
import { OnBufferingUpdateListener } from '[@ohos](/user/ohos)/ijkplayer';
import { OnErrorListener, OnTimedTextListener } from '[@ohos](/user/ohos)/ijkplayer';
import { OnInfoListener } from '[@ohos](/user/ohos)/ijkplayer';
import { OnSeekCompleteListener } from '[@ohos](/user/ohos)/ijkplayer';
import { LogUtils } from '[@ohos](/user/ohos)/ijkplayer';
import { PlayStatus } from './PlayStatus';
import abilityAccessCtrl from '[@ohos](/user/ohos).abilityAccessCtrl';
import photoAccessHelper from '[@ohos](/user/ohos).file.photoAccessHelper';
import fs from '[@ohos](/user/ohos).file.fs';
import { IjkplayerMapManager } from './IjkplayerMapManager';
[@Entry](/user/Entry)
[@Component](/user/Component)
struct IjkVideoPlayerPage {
[@State](/user/State) progressValue: number = 0;
[@State](/user/State) currentTime: string = "00:00";
[@State](/user/State) totalTime: string = "00:00";
[@State](/user/State) loadingVisible: Visibility = Visibility.None;
[@State](/user/State) replayVisible: Visibility = Visibility.None;
[@State](/user/State) slideEnable: boolean = false;
[@State](/user/State) aspRatio: number = 0.5;
[@State](/user/State) mContext: object | undefined = undefined;
[@State](/user/State) mFirst: boolean = true;
[@State](/user/State) mDestroyPage: boolean = false;
[@State](/user/State) playSpeed: string = '1f';
[@State](/user/State) volume: number = 1.0;
[@State](/user/State) oldSeconds: number = 0;
[@State](/user/State) isSeekTo: boolean = false;
[@State](/user/State) isCurrentTime: boolean = false;
[@State](/user/State) videoWidth: string = '100%';
[@State](/user/State) initAspectRatio: number = 1;
[@State](/user/State) videoAspectRatio: number = this.initAspectRatio;
// 替换为自己的rtmp
private videoUrl: string = 'rtmp://xxxxxxx';
[@State](/user/State) videoParentAspectRatio: number = this.initAspectRatio;
private mIjkMediaPlayer = IjkMediaPlayer.getInstance();
private CONTROL_PlayStatus = PlayStatus.INIT;
[@State](/user/State) PROGRESS_MAX_VALUE: number = 100;
[@State](/user/State) updateProgressTimer: number = 0;
[@State](/user/State) curIndex: number = 0;
[@State](/user/State) recordProgressVisible: Visibility = Visibility.None;
[@State](/user/State) screenshotProgressVisible: Visibility = Visibility.None;
[@State](/user/State) recordSaveFilePath: string = "";
private xcomponentId: string = ""
aboutToAppear() {
LogUtils.getInstance().LOGI("aboutToAppear");
this.xcomponentId = "xcomponentId_" + IjkplayerMapManager.generateId();
// this.videoUrl = (this.getUIContext().getRouter().getParams() as RouterParam).videoUrl;
let event: Callback<InterruptEvent> = (event) => {
LogUtils.getInstance().LOGI(`event: ${JSON.stringify(event)}`);
if (event.hintType === InterruptHintType.INTERRUPT_HINT_PAUSE) {
this.pause();
} else if (event.hintType === InterruptHintType.INTERRUPT_HINT_RESUME) {
this.startPlayOrResumePlay();
} else if (event.hintType === InterruptHintType.INTERRUPT_HINT_STOP) {
this.stop();
}
}
this.mIjkMediaPlayer.on('audioInterrupt', event);
let deviceChangeEvent: Callback<InterruptEvent> = (event) => {
LogUtils.getInstance().LOGI(`deviceChange event: ${JSON.stringify(event)}`);
if (event.reason === DeviceChangeReason.REASON_NEW_DEVICE_AVAILABLE) {
this.pause();
} else if (event.reason === DeviceChangeReason.REASON_OLD_DEVICE_UNAVAILABLE) {
this.pause();
}
}
this.mIjkMediaPlayer.on('deviceChange', deviceChangeEvent);
}
aboutToDisappear() {
LogUtils.getInstance().LOGI("aboutToDisappear");
IjkplayerMapManager.getInstance().destroyStatusMap.set(this.xcomponentId, true);
this.mDestroyPage = true;
this.mIjkMediaPlayer.setScreenOnWhilePlaying(false);
this.stopRecord(false);
if (this.CONTROL_PlayStatus !== PlayStatus.INIT) {
this.stop();
}
this.mIjkMediaPlayer.off('audioInterrupt');
this.mIjkMediaPlayer.off('deviceChange');
}
onPageShow() {
if (this.mContext && !this.mFirst) {
this.startPlayOrResumePlay();
}
}
onPageHide() {
this.pause();
}
xcomponentController: XComponentController = new XComponentController()
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Auto, justifyContent: FlexAlign.Start }) {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('ijk_player')
.fontSize('30px')
.fontColor(Color.White)
.margin('10px')
.fontWeight(FontWeight.Bold)
}.height('100px').width('100%').backgroundColor(Color.Black)
Divider().vertical(false).strokeWidth('20px').color(Color.White).lineCap(LineCapStyle.Round)
Stack({ alignContent: Alignment.Center }) {
Column(){
XComponent({
id: this.xcomponentId,
type: XComponentType.SURFACE,
libraryname: 'ijkplayer_napi'
})
.onLoad((event?: object) => {
if (!!event) {
this.initDelayPlay(event);
}
})
.onDestroy(() => {
})
.width('100%')
.width(this.videoWidth)
.aspectRatio(this.videoAspectRatio)
.id(this.xcomponentId)
}.aspectRatio(this.videoAspectRatio)
Image($r('app.media.startIcon'))
.objectFit(ImageFit.Auto)
.width('120px')
.height('120px')
.visibility(this.replayVisible)
.border({ width: 0 })
.borderStyle(BorderStyle.Dashed)
.onClick(() => {
this.startPlayOrResumePlay();
})
Image($r('app.media.icon_load'))
.objectFit(ImageFit.Auto)
.width('120px')
.height('120px')
.visibility(this.loadingVisible)
.border({ width: 0 })
.borderStyle(BorderStyle.Dashed)
}.width('100%').backgroundColor('#000000').clip(true)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
Text(this.currentTime).width('100px').fontSize('20px').margin('20px')
Slider({
value: this.progressValue,
min: 0,
max: this.PROGRESS_MAX_VALUE,
step: 1,
style: SliderStyle.OutSet
})
.width('600px')
.blockColor(Color.Blue)
.trackColor(Color.Gray)
.selectedColor(Color.Blue)
.showSteps(true)
.showTips(true)
.enabled(this.slideEnable)
.onChange((value: number, mode: SliderChangeMode) => {
if (mode === 2) {
this.isSeekTo = true;
this.mDestroyPage = false;
this.showLoadIng();
LogUtils.getInstance().LOGI("slider-->seekValue start:" + value);
let seekValue = value * (this.mIjkMediaPlayer.getDuration() / 100);
this.seekTo(seekValue + "");
this.setProgress()
LogUtils.getInstance().LOGI("slider-->seekValue end:" + seekValue);
this.isSeekTo = false;
}
})
Text(this.totalTime).width('100px').fontSize('20px').margin('10px')
}
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
Button('Play')
.onClick(() => {
this.startPlayOrResumePlay();
})
.width('400px')
.height('80px')
.margin('15px')
Button('Pause')
.onClick(() => {
this.pause();
})
.width('400px')
.height('80px')
.margin('15px')
}
}
}
private initDelayPlay(context: object) {
this.mContext = context;
setTimeout(() => {
this.startPlayOrResumePlay();
this.mFirst = false;
}, 300)
}
private startPlayOrResumePlay() {
this.mDestroyPage = false;
LogUtils.getInstance().LOGI("startPlayOrResumePlay start this.CONTROL_PlayStatus:" + this.CONTROL_PlayStatus)
if (this.CONTROL_PlayStatus === PlayStatus.INIT) {
this.stopProgressTask();
this.startProgressTask();
this.play(this.videoUrl.toString());
}
if (this.CONTROL_PlayStatus === PlayStatus.PAUSE) {
this.mIjkMediaPlayer.start();
this.setProgress()
}
}
private completionNum(num: number): string | number {
if (num < 10) {
return '0' + num;
} else {
return num;
}
}
private stringForTime(timeMs: number): string {
let totalSeconds: number | string = (timeMs / 1000);
let newSeconds: number | string = totalSeconds % 60;
let minutes: number | string = (totalSeconds / 60) % 60;
let hours: number | string = totalSeconds / 3600;
LogUtils.getInstance().LOGI("stringForTime hours:" + hours + ",minutes:" + minutes + ",seconds:" + newSeconds);
hours = this.completionNum(Math.floor(Math.floor(hours * 100) / 100));
minutes = this.completionNum(Math.floor(Math.floor(minutes * 100) / 100));
newSeconds = Math.floor(Math.floor(newSeconds * 100) / 100)
if (this.isCurrentTime) {
if (this.oldSeconds < newSeconds || newSeconds === 0 || this.isSeekTo) {
this.oldSeconds = newSeconds
} else {
newSeconds = this.oldSeconds
}
}
newSeconds = this.completionNum(newSeconds);
if (hours > 0) {
return hours + ":" + minutes + ":" + newSeconds;
} else {
return minutes + ":" + newSeconds;
}
}
private setProgress() {
if (IjkplayerMapManager.getInstance().destroyStatusMap.get(this.xcomponentId)) {
return;
}
if (!IjkplayerMapManager.getInstance().playStatusMap.get(this.xcomponentId)) {
return;
}
let position = this.mIjkMediaPlayer.getCurrentPosition();
let duration = this.mIjkMediaPlayer.getDuration();
let pos = 0;
if (duration > 0) {
this.slideEnable = true;
let curPercent = position / duration;
pos = curPercent * 100;
if (pos > this.PROGRESS_MAX_VALUE) {
this.progressValue = this.PROGRESS_MAX_VALUE
} else {
this.progressValue = pos;
}
}
LogUtils.getInstance()
.LOGI("setProgress position:" + position + ",duration:" + duration + ",progressValue:" + pos);
this.totalTime = this.stringForTime(duration);
if (position > duration) {
position = duration;
}
this.isCurrentTime = true;
this.currentTime = this.stringForTime(position);
this.isCurrentTime = false
}
private startProgressTask() {
this.updateProgressTimer = setInterval(() => {
LogUtils.getInstance().LOGI("startProgressTask");
if (IjkplayerMapManager.getInstance().destroyStatusMap.get(this.xcomponentId)) {
clearInterval(this.updateProgressTimer);
return;
}
if (!this.mDestroyPage) {
this.setProgress();
}
}, 300);
}
private stopProgressTask() {
LogUtils.getInstance().LOGI("stopProgressTask");
clearInterval(this.updateProgressTimer);
}
private showLoadIng() {
this.loadingVisible = Visibility.Visible;
this.replayVisible = Visibility.None;
}
private hideLoadIng() {
this.loadingVisible = Visibility.None;
this.replayVisible = Visibility.None;
}
private showRePlay() {
this.loadingVisible = Visibility.None;
this.replayVisible = Visibility.Visible;
}
private play(url: string) {
if (IjkplayerMapManager.getInstance().destroyStatusMap.get(this.xcomponentId)) {
return;
}
this.showLoadIng();
//设置XComponent回调的context
if (!!this.mContext) {
this.mIjkMediaPlayer.setContext(this.mContext, this.xcomponentId);
}
if (this.CONTROL_PlayStatus === PlayStatus.INIT) {
this.mIjkMediaPlayer.reset();
}
this.CONTROL_PlayStatus = PlayStatus.PLAY;
//设置debug模式
this.mIjkMediaPlayer.setDebug(true);
//初始化配置
this.mIjkMediaPlayer.native_setup();
// 初始化配置后需要重新设置音频流音量,否则音量为默认值1.0
this.mIjkMediaPlayer.setVolume(this.volume.toString(), this.volume.toString());
//设置视频源
this.mIjkMediaPlayer.setDataSource(url);
//设置视频源http请求头
//使用精确寻帧 例如,拖动播放后,会寻找最近的关键帧进行播放,很有可能关键帧的位置不是拖动后的位置,而是较前的位置.可以设置这个参数来解决问题
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", "1");
//预读数据的缓冲区大小
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", "102400");
//停止预读的最小帧数
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", "100");
//启动预加载
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", "1");
// 设置无缓冲,这是播放器的缓冲区,有数据就播放
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", "0");
//跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "5");
// 最大缓冲cache是3s, 有时候网络波动,会突然在短时间内收到好几秒的数据
// 因此需要播放器丢包,才不会累积延时
// 这个和第三个参数packet-buffering无关。
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000");
// 无限制收流
// this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1");
// this.mIjkMediaPlayer.setOptionLong(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", "1")
// 屏幕常亮
this.mIjkMediaPlayer.setScreenOnWhilePlaying(true);
// 设置超时
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", "10000000");
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "connect_timeout", "10000000");
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "addrinfo_timeout", "10000000");
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", "10000000");
// 变速播放
this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", "1");
this.mIjkMediaPlayer.setSpeed(this.playSpeed);
let Speed = this.mIjkMediaPlayer.getSpeed()
LogUtils.getInstance().LOGI('getSpeed--' + Speed)
//是否开启循环播放
this.mIjkMediaPlayer.setLoopCount(false);
let mOnVideoSizeChangedListener: OnVideoSizeChangedListener = {
onVideoSizeChanged: (width: number, height: number, sar_num: number, sar_den: number) => {
if (height === 0) {
return;
}
const va = width / height;
const vpa = this.videoParentAspectRatio;
if (vpa > va) {
this.videoWidth = (width / (height * vpa)) * 100 + '%';
} else {
this.videoWidth = '100%';
}
if (width && height) {
this.videoAspectRatio = width / height;
}
LogUtils.getInstance()
.LOGI("setOnVideoSizeChangedListener更多关于HarmonyOS鸿蒙Next中求助,求一个播放rtmp://的案例的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
[https://ohpm.openharmony.cn/#/cn/detail/@nodemedia%2Fnodemediaclient](https://ohpm.openharmony.cn/#/cn/detail/@nodemedia%2Fnodemediaclient)
看看这个
鸿蒙Next中播放RTMP流可使用AVPlayer组件。在ets文件中导入media模块,创建AVPlayer实例,设置url为RTMP地址,调用prepare()和play()方法即可播放。需在module.json5中声明ohos.permission.INTERNET网络权限。
在HarmonyOS Next中播放RTMP流,可以使用VideoPlayer组件结合AVPlayer实现。以下是核心代码示例:
- 创建AVPlayer实例:
import avSession from '@ohos.multimedia.avsession';
import media from '@ohos.multimedia.media';
let avPlayer: media.AVPlayer;
avPlayer = await media.createAVPlayer();
- 配置并播放RTMP:
// 设置播放源
avPlayer.url = 'rtmp://example.com/live/stream';
// 准备播放
await avPlayer.prepare();
await avPlayer.play();
- UI层使用VideoPlayer组件:
VideoPlayer({
src: 'rtmp://example.com/live/stream',
controller: this.videoController
})
.width('100%')
.height(300)
- 关键配置:
- 在
module.json5中添加网络权限:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
- 确保
AVPlayer支持RTMP协议(HarmonyOS Next已内置支持)
- 完整生命周期管理:
// 暂停
avPlayer.pause();
// 停止
avPlayer.stop();
avPlayer.release();
注意:RTMP流媒体播放需要稳定的网络连接,建议添加网络状态监听和错误处理逻辑。实际播放效果取决于RTMP服务器配置和网络环境。

