HarmonyOS鸿蒙Next中求助,求一个播放rtmp://的案例

HarmonyOS鸿蒙Next中求助,求一个播放rtmp://的案例

需要一个rtmp的案例,开箱即用那种,感谢

4 回复

【解决方案】

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


鸿蒙Next中播放RTMP流可使用AVPlayer组件。在ets文件中导入media模块,创建AVPlayer实例,设置url为RTMP地址,调用prepare()和play()方法即可播放。需在module.json5中声明ohos.permission.INTERNET网络权限。

在HarmonyOS Next中播放RTMP流,可以使用VideoPlayer组件结合AVPlayer实现。以下是核心代码示例:

  1. 创建AVPlayer实例
import avSession from '@ohos.multimedia.avsession';
import media from '@ohos.multimedia.media';

let avPlayer: media.AVPlayer;
avPlayer = await media.createAVPlayer();
  1. 配置并播放RTMP
// 设置播放源
avPlayer.url = 'rtmp://example.com/live/stream';

// 准备播放
await avPlayer.prepare();
await avPlayer.play();
  1. UI层使用VideoPlayer组件
VideoPlayer({
  src: 'rtmp://example.com/live/stream',
  controller: this.videoController
})
.width('100%')
.height(300)
  1. 关键配置
  • module.json5中添加网络权限:
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  }
]
  • 确保AVPlayer支持RTMP协议(HarmonyOS Next已内置支持)
  1. 完整生命周期管理
// 暂停
avPlayer.pause();

// 停止
avPlayer.stop();
avPlayer.release();

注意:RTMP流媒体播放需要稳定的网络连接,建议添加网络状态监听和错误处理逻辑。实际播放效果取决于RTMP服务器配置和网络环境。

回到顶部