HarmonyOS 鸿蒙Next中5.1视频avplayer播放rtsp

HarmonyOS 鸿蒙Next中5.1视频avplayer播放rtsp 鸿蒙4.1api11使用avplayer去播放rtsp视频流能够正常播放,现在要适配到5.1api18却报格式不支持的错误,目前鸿蒙5.1使用什么方式可以播放rtsp视频流?

cke_834.png


更多关于HarmonyOS 鸿蒙Next中5.1视频avplayer播放rtsp的实战教程也可以访问 https://www.itying.com/category-93-b0.html

7 回复

【解决方案】

开发者您好,HarmonyOS暂无RTSP视频流播放能力,后续迭代以官网更新为准。

当前可以使用 ijkplayer 三方库实现:ijkplayer 是 OpenHarmony 环境下可用的一款基于 FFmpeg 的视频播放器,支持视频播放、音频播放、倍速播放、硬解码、视频录制、截屏、HLS/RTSP/RTMP 直播流等功能。

更多关于HarmonyOS 鸿蒙Next中5.1视频avplayer播放rtsp的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


cke_6994.png cke_411.png cke_817.png

你好报这个错误是不是缺少了什么关键配置,有没有详细的参考文档

开发者您好,可以参考以下文章排查是否是三方库依赖缺失导致工程正常编译运行报错
https://developer.huawei.com/consumer/cn/forum/topic/0202194087106713089?fid=0109140870620153026
若是不能解决您的问题,麻烦请提供可运行能复现问题的最小demo或工程以及完整的hilog日志

import { IjkMediaPlayer } from "@ohos/ijkplayer";
import type { OnPreparedListener } from "@ohos/ijkplayer";
import type { OnVideoSizeChangedListener } from "@ohos/ijkplayer";
import type { OnCompletionListener } from "@ohos/ijkplayer";
import type { OnBufferingUpdateListener } from "@ohos/ijkplayer";
import type { OnErrorListener } from "@ohos/ijkplayer";
import type { OnInfoListener } from "@ohos/ijkplayer";
import type { OnSeekCompleteListener } from "@ohos/ijkplayer";
import hilog from '@ohos.hilog';

const JKPLAYER_LOG_DOMAIN: number = 0xF005;
const JKPLAYER_LOG_TAG: string = 'jkplayer';

@Entry
@Component
struct IjkVideoPlayerPage {
  @State aspRatio: number = 16 / 9;
  @State loadingVisible: Visibility = Visibility.Visible;
  @State currentTime: string = "00:00";
  @State progressValue: number = 0;
  @State PROGRESS_MAX_VALUE: number = 100;

  private mIjkMediaPlayer: IjkMediaPlayer = IjkMediaPlayer.getInstance();
  private mContext: object | undefined = undefined;
  //rtsp地址
  private videoUrl: string = "";

  build() {
    Column() {
      Text('视频播放器')
        .fontSize('30px')
        .fontColor(Color.White)
        .margin('20px')
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .textAlign(TextAlign.Center)
        .backgroundColor(Color.Black)

      Stack({ alignContent: Alignment.Center }) {
        XComponent({
          id: 'xcomponentId',
          type: 'surface',
          libraryname: 'ijkplayer_napi'
        })
          .onLoad((context) => {
            this.mContext = context;
            if (context) {
              this.jkLog('XComponent onLoad, context ok, init player');
              this.initDelayPlay(context);
            } else {
              this.jkLog('XComponent onLoad, context is empty');
            }
          })
          .onDestroy(() => {
            this.jkLog('XComponent onDestroy');
            this.releasePlayer();
          })
          .width('100%')
          .aspectRatio(this.aspRatio)

        Text('加载中...')
          .fontSize('24px')
          .fontColor(Color.White)
          .visibility(this.loadingVisible)
      }
      .backgroundColor('#000000')
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Black)
  }

  private jkLog(msg: string): void {
    hilog.info(JKPLAYER_LOG_DOMAIN, JKPLAYER_LOG_TAG, '%{public}s', `jkplayer ${msg}`);
  }

  private jkLogError(msg: string): void {
    hilog.error(JKPLAYER_LOG_DOMAIN, JKPLAYER_LOG_TAG, '%{public}s', `jkplayer ${msg}`);
  }

  private initDelayPlay(context: object) {
    let that = this;

    this.jkLog(`initDelayPlay begin, url=${this.videoUrl}`);
    this.mIjkMediaPlayer.setContext(context, "xcomponentId");
    this.mIjkMediaPlayer.setDebug(true);
    this.mIjkMediaPlayer.native_setup();
    this.mIjkMediaPlayer.setDataSource(this.videoUrl);
    this.jkLog('setDataSource done');

    let headers = new Map<string, string>([
      ["user_agent", "Mozilla/5.0 BiliDroid/7.30.0 (bbcallen@gmail.com)"],
      ["referer", "https://www.bilibili.com"]
    ]);
    this.mIjkMediaPlayer.setDataSourceHeader(headers);

    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");
    this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", "5");
    this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", "3000");
    this.mIjkMediaPlayer.setOption(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, "listen_timeout", "10000000");
    this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "addrinfo_timeout", "10000000");
    this.mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", "10000000");

    let mOnVideoSizeChangedListener: OnVideoSizeChangedListener = {
      onVideoSizeChanged(width: number, height: number, sar_num: number, sar_den: number) {
        that.aspRatio = width / height;
        that.jkLog(`onVideoSizeChanged width=${width} height=${height} sar_num=${sar_num} sar_den=${sar_den}`);
        that.hideLoadIng();
      }
    };
    this.mIjkMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);

    let mOnPreparedListener: OnPreparedListener = {
      onPrepared() {
        let d = that.mIjkMediaPlayer.getDuration();
        that.jkLog(`onPrepared durationMs=${d}`);
      }
    };
    this.mIjkMediaPlayer.setOnPreparedListener(mOnPreparedListener);

    let mOnCompletionListener: OnCompletionListener = {
      onCompletion() {
        that.jkLog('onCompletion');
        that.currentTime = that.stringForTime(that.mIjkMediaPlayer.getDuration());
        that.progressValue = that.PROGRESS_MAX_VALUE;
        that.stop();
      }
    };
    this.mIjkMediaPlayer.setOnCompletionListener(mOnCompletionListener);

    let mOnBufferingUpdateListener: OnBufferingUpdateListener = {
      onBufferingUpdate(percent: number) {
        that.jkLog(`onBufferingUpdate percent=${percent}`);
      }
    };
    this.mIjkMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);

    let mOnSeekCompleteListener: OnSeekCompleteListener = {
      onSeekComplete() {
        that.jkLog('onSeekComplete');
        that.startPlayOrResumePlay();
      }
    };
    this.mIjkMediaPlayer.setOnSeekCompleteListener(mOnSeekCompleteListener);

    let mOnInfoListener: OnInfoListener = {
      onInfo(what: number, extra: number) {
        that.jkLog(`onInfo what=${what} extra=${extra}`);
      }
    };
    this.mIjkMediaPlayer.setOnInfoListener(mOnInfoListener);

    let mOnErrorListener: OnErrorListener = {
      onError(what: number, extra: number) {
        that.jkLogError(`onError what=${what} extra=${extra}`);
        that.hideLoadIng();
      }
    };
    this.mIjkMediaPlayer.setOnErrorListener(mOnErrorListener);

    this.mIjkMediaPlayer.setMessageListener();
    this.jkLog('prepareAsync');
    this.mIjkMediaPlayer.prepareAsync();
    this.jkLog('start called after prepareAsync');
    this.mIjkMediaPlayer.start();
    this.jkLog(`initDelayPlay end isPlaying=${this.mIjkMediaPlayer.isPlaying()}`);
  }

  private hideLoadIng() {
    this.loadingVisible = Visibility.None;
    this.jkLog('hide loading overlay');
  }

  private stringForTime(timeMs: number): string {
    let totalSeconds = Math.floor(timeMs / 1000);
    let minutes = Math.floor(totalSeconds / 60);
    let seconds = totalSeconds % 60;
    return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }

  private stop() {
    if (this.mIjkMediaPlayer.isPlaying()) {
      this.jkLog('stop: pause (was playing)');
      this.mIjkMediaPlayer.pause();
    } else {
      this.jkLog('stop: skip pause (not playing)');
    }
  }

  private startPlayOrResumePlay() {
    if (!this.mIjkMediaPlayer.isPlaying()) {
      this.jkLog('startPlayOrResumePlay: start()');
      this.mIjkMediaPlayer.start();
    } else {
      this.jkLog('startPlayOrResumePlay: already playing');
    }
  }

  private releasePlayer() {
    try {
      this.jkLog('releasePlayer: stop + release');
      this.mIjkMediaPlayer.stop();
      this.mIjkMediaPlayer.release();
      this.jkLog('releasePlayer: ok');
    } catch (e) {
      this.jkLogError(`releasePlayer error: ${e}`);
    }
  }
}

这个是完整的组件demo,avplayer包是下载的

cke_127158.png

开发者您好,当前本地运行测试您提供的demo可以播放rtsp视频源。
测试环境:API 23、DevEco Studio 6.1.0 Release
注意要通过包管理工具ohpm install的方式安装ijkplayer的依赖:ohpm i @ohos/ijkplayer。
若不能解决您的问题,欢迎再次反馈。

鸿蒙Next中avplayer不支持rtsp协议,仅支持http/https/hls等格式。rtsp流无法直接使用avplayer播放。

在 API 18 中,AVPlayer 已不再直接支持 RTSP 协议,这是报 “格式不支持” 的根本原因。如需继续播放 RTSP 流,可以选择以下通用方案:

  • 转流为 HLS:用 ffmpeg 等工具将 RTSP 流转封装为 HLS,再通过 AVPlayer 播放,这是最稳定且兼容官方接口的方式。
  • 集成第三方播放器:使用移植到鸿蒙的 ijkplayer 或 GSYVideoPlayer 等支持 RTSP 的第三方播放器,通过 XComponent 渲染。
  • 自实现数据源与解封装:借助 AVPlayer 的自定义数据源(AVDataSource)和 Demuxer,自行拉取 RTSP 流并解封装为 AVPlayer 支持的封装格式(如 TS),但实现复杂度较高。
  • NDK + FFmpeg 软解码:编写 Native 模块拉流、解码后,通过 XComponent 把图像帧渲染到屏幕,具备完全控制能力。

推荐优先采用 HLS 转流方案,无需修改播放端逻辑即可适配 API 18。

回到顶部