[文章]基于HarmonyOS Player,实现视频文件的播放

阅读量0
0
5
1. 介绍      
播放视频的多媒体应用程序通常包含两个部分:
  • 给定媒体源的播放器加载媒体资源,并通过Surface来进行画面渲染,将其呈现为视频。
  • 具有传输控件的用户界面(UI),以承载播放器并显示播放器的状态。
    本应用程序原理图如下图:

本篇Codelab将实现的内容
本篇Codelab旨在让开发者了解手机HarmonyOS应用开发,常用布局、典型控件、FA组件、媒体-视频、跨设备协同的体验以及从工程创建到代码和布局的编写,再到编译构建、部署和运行全过程。

您将构建一个基于HarmonyOS Player类实现的应用程序,该应用程序功能为播放本地视频资源或从Internet获得的视频资源。效果图如下:


您将会学到什么
  • 如何使用Player类播放视频
  • 如何使用自定义控件来控制视频播放
  • 如何添加并使用媒体事件的事件侦听器和回调

2. 搭建HarmonyOS环境
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
  • 安装DevEco Studio,详情请参考下载和安装软件。
  • 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
    • 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
    • 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
  • 开发者可以参考以下链接,完成设备调试的相关配置:
    • 使用真机进行调试
    • 使用模拟器进行调试
  

3. 代码结构      
本篇Codelab只对核心代码进行讲解,对于完整代码,我们在7 参考提供下载方式。接下来我们会讲解整个工程的代码结构,如下图:
  • api:视频播放状态改变及屏幕状态变化监听。
  • constant:定义视频状态、进度条和控制器状态。
  • factoty:创建SourceFactory类来根据视频来源创建视频源。
  • manager:创建HmPlayerLifecycle来处理Player类的生命周期。
  • view:创建PlayerLoading、SimplePlayerController类分别为视频加载状态及进度条控制类文件。
  • HmPlayer:封装播放器的主要功能方法。
  • slice:创建MainAbilitySlice、SimplePlayerAbilitySlice分别为进入应用的主程序页面和视频播放页面。
  • utils:存放所有封装好的公共方法,如DateUtils,LogUtils等。
  • resources:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件;resourcesbasemedia下存放视频文件。
  • config.json:Ability声明及权限配置。
   

4. 创建视频播放业务逻辑      
该应用程序可播放的视频格式包括mp4、mov、3gp、mkv,首先准备一份视频文件并复制到"resources/base/layout/media"文件目录。下面将会介绍视频列表布局及播放逻辑。
创建视频播放页面文件及布局步骤 1 -  创建simple_video_play_layout.xml布局文件展示视频列表。
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <DependentLayout
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos"
  4.     ohos:id="$+id:parent"
  5.     ohos:height="match_parent"
  6.     ohos:width="match_parent"
  7.     ohos:background_element="#ffffff"
  8.     ohos:alignment="center">

  9.     <ohos.codelabs.video.player.view.PlayerView
  10.         ohos:id="$+id:player_view"
  11.         ohos:height="match_parent"
  12.         ohos:width="match_parent"/>

  13.     <ohos.codelabs.video.player.view.PlayerLoading
  14.         ohos:id="$+id:loading_view"
  15.         ohos:height="match_parent"
  16.         ohos:width="match_parent"/>

  17.     <ohos.codelabs.video.player.view.SimplePlayerController
  18.         ohos:id="$+id:controller_view"
  19.         ohos:height="match_parent"
  20.         ohos:width="match_parent"/>

  21. </DependentLayout>
复制代码
步骤 2 -  创建SimplePlayerAbilitySlice类,初次创建该页面进行初始化。
  1. private String url = "entry/resources/base/media/gubeishuizhen.mp4";
  2. [url=home.php?mod=space&uid=2735960]@Override[/url]
  3. public void onStart(Intent intent) {
  4.     super.onStart(intent);
  5.     super.setUIContent(ResourceTable.Layout_simple_video_play_layout);
  6.     player = new HmPlayer.Builder(this).setFilePath(url).create();
  7.     player.getLifecycle().onStart();
  8.     initComponent();
  9. }
复制代码
将预置的视频资源初始化为url对象,并通过initComponent方法对视频播放的控件进行初始化及赋值。
  1. private void initComponent() {
  2.     if (findComponentById(ResourceTable.Id_parent) instanceof DependentLayout) {
  3.         parentLayout = (DependentLayout) findComponentById(ResourceTable.Id_parent);
  4.     }
  5.     if (findComponentById(ResourceTable.Id_player_view) instanceof PlayerView) {
  6.         playerView = (PlayerView) findComponentById(ResourceTable.Id_player_view);
  7.     }
  8.     if (findComponentById(ResourceTable.Id_loading_view) instanceof PlayerLoading) {
  9.         loadingView = (PlayerLoading) findComponentById(ResourceTable.Id_loading_view);
  10.     }
  11.     if (findComponentById(ResourceTable.Id_controller_view) instanceof SimplePlayerController) {
  12.         controllerView = (SimplePlayerController) findComponentById(ResourceTable.Id_controller_view);
  13.     }
  14.     playerView.bind(player);
  15.     loadingView.bind(player);
  16.     controllerView.bind(player);
  17. }
复制代码
创建HmPlayer

HmPlayer类是以HarmonyOS Player为基础封装了复杂播放流程并对外开放ImPlayer接口类以实现所有支持的功能。
  1. /**
  2. * IPlayer interface
  3. *
  4. * @since 2021-04-04
  5. */
  6. public interface ImplPlayer {
  7.     /**
  8.      * addSurface
  9.      *
  10.      * [url=home.php?mod=space&uid=3142012]@param[/url] surface surface
  11.      */
  12.     void addSurface(Surface surface);

  13.     /**
  14.      * addPlayerStatuCallback
  15.      *
  16.      * @param callback callback
  17.      */
  18.     void addPlayerStatuCallback(StatuChangeListener callback);

  19.     /**
  20.      * removePlayerStatuCallback
  21.      *
  22.      * @param callback callback
  23.      */
  24.     void removePlayerStatuCallback(StatuChangeListener callback);

  25.     /**
  26.      * addPlayerViewCallback
  27.      *
  28.      * @param callback callback
  29.      */
  30.     void addPlayerViewCallback(ScreenChangeListener callback);

  31.     /**
  32.      * removePlayerViewCallback
  33.      *
  34.      * @param callback callback
  35.      */
  36.     void removePlayerViewCallback(ScreenChangeListener callback);

  37.     /**
  38.      * play
  39.      */
  40.     void play();

  41.     /**
  42.      * replay
  43.      */
  44.     void replay();

  45.     /**
  46.      * reload
  47.      *
  48.      * @param filepath filepath
  49.      * @param startMillisecond startMillisecond
  50.      */
  51.     void reload(String filepath, int startMillisecond);

  52.     /**
  53.      * resume
  54.      */
  55.     void resume();

  56.     /**
  57.      * pause
  58.      */
  59.     void pause();

  60.     /**
  61.      * getCurrentPosition
  62.      *
  63.      * [url=home.php?mod=space&uid=1141835]@Return[/url] current position
  64.      */
  65.     int getCurrentPosition();

  66.     /**
  67.      * getDuration
  68.      *
  69.      * @return duration
  70.      */
  71.     int getDuration();

  72.     /**
  73.      * getVolume
  74.      *
  75.      * @return float
  76.      */
  77.     float getVolume();

  78.     /**
  79.      * set play volume
  80.      *
  81.      * @param volume 0~1
  82.      */
  83.     void setVolume(float volume);

  84.     /**
  85.      * set play speed
  86.      *
  87.      * @param speed 0~12
  88.      */
  89.     void setPlaySpeed(float speed);

  90.     /**
  91.      * getVideoScale
  92.      *
  93.      * @return double
  94.      */
  95.     double getVideoScale();

  96.     /**
  97.      * rewindTo
  98.      *
  99.      * @param startMicrosecond startMicrosecond(ms)
  100.      */
  101.     void rewindTo(int startMicrosecond);

  102.     /**
  103.      * isPlaying
  104.      *
  105.      * @return isPlaying
  106.      */
  107.     boolean isPlaying();

  108.     /**
  109.      * stop
  110.      */
  111.     void stop();

  112.     /**
  113.      * release
  114.      */
  115.     void release();

  116.     /**
  117.      * getLifecycle
  118.      *
  119.      * @return ImplLifecycle
  120.      */
  121.     ImplLifecycle getLifecycle();

  122.     /**
  123.      * getBuilder
  124.      *
  125.      * @return Builder
  126.      */
  127.     HmPlayer.Builder getBuilder();

  128.     /**
  129.      * getPlayerStatu
  130.      *
  131.      * @return PlayerStatu
  132.      */
  133.     PlayerStatu getPlayerStatu();

  134.     /**
  135.      * resizeScreen
  136.      *
  137.      * @param width width
  138.      * @param height height
  139.      */
  140.     void resizeScreen(int width, int height);

  141.     /**
  142.      * openGesture
  143.      *
  144.      * @param isOpen isOpen
  145.      */
  146.     void openGesture(boolean isOpen);

  147.     /**
  148.      * openGesture
  149.      *
  150.      * @return isGestureOpen
  151.      */
  152.     boolean isGestureOpen();
  153. }
复制代码
通过HmPlayer.Builder构造器设置播放源、开始时间等播放参数并初始化HmPlayer。
  1. player = new HmPlayer.Builder(this).setFilePath(url).create();
复制代码
添加播放器状态、UI变化监听,在监听中处理逻辑。
  1. player.addPlayerStatuCallback(statu -> mContext.getUITaskDispatcher().asyncDispatch(() -> {
  2.     switch (statu) {
  3.         case PREPARING:
  4.         case BUFFERING:
  5.             show();
  6.             break;
  7.         case PLAY:
  8.             hide();
  9.             break;
  10.         default:
  11.             break;
  12.     }
  13. }));

  14. player.addPlayerViewCallback((width, height) -> mContext.getUITaskDispatcher().asyncDispatch(() -> {
  15.     if (width > 0) {
  16.         setWidth(width);
  17.     }
  18.     if (height > 0) {
  19.         setHeight(height);
  20.     }
  21. }));
复制代码
设置播放器生命周期与slice生命周期一致。
  1. @Override
  2. public void onStart(Intent intent) {
  3.     super.onStart(intent);
  4.         ...
  5.     player.getLifecycle().onStart();
  6. }

  7. @Override
  8. public void onForeground(Intent intent) {
  9.     player.getLifecycle().onForeground();
  10.     super.onForeground(intent);
  11. }

  12. @Override
  13. protected void onBackground() {
  14.     player.getLifecycle().onBackground();
  15.     super.onBackground();
  16. }

  17. @Override
  18. protected void onStop() {
  19.         ...
  20.     player.getLifecycle().onStop();
  21.     super.onStop();
  22. }
复制代码
如果您还不了解HarmonyOS Player,请参考视频播放开发指导。
创建PlayerView

图像渲染在屏幕上需要使用SurfaceProvider,该类控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等。当底层显示系统第一次创建surface之后会调用surfaceCreated(SurfaceOps surfaceOps)回调函数。
  1. surfaceView.getSurfaceOps().ifPresent(surfaceOps -> surfaceOps.addCallback(new SurfaceOps.Callback() {
  2.     @Override
  3.     public void surfaceCreated(SurfaceOps surfaceOps) {
  4.         surface = surfaceOps.getSurface();
  5.         if (player != null) {
  6.             player.addSurface(surface);
  7.         }
  8.     }

  9.     @Override
  10.     public void surfaceChanged(SurfaceOps surfaceOps, int info, int width, int height) {
  11.     }

  12.     @Override
  13.     public void surfaceDestroyed(SurfaceOps surfaceOps) {
  14.     }
  15. }));
复制代码
PlayerView绑定HmPlayer:
  1. if (findComponentById(ResourceTable.Id_player_view) instanceof PlayerView) {
  2.     playerView = (PlayerView) findComponentById(ResourceTable.Id_player_view);
  3. }
  4. playerView.bind(player);
复制代码
视频尺寸与播放框架尺寸适配。
  1. @Override
  2. public void onRefreshed(Component component) {
  3.     int newWidth = component.getWidth();
  4.     int newHeight = component.getHeight();
  5.     double videoScale = player.getVideoScale();
  6.     if (videoScale != Constants.NUMBER_NEGATIVE_1 && (newWidth != viewWidth || newHeight != viewHeight)) {
  7.         viewWidth = newWidth;
  8.         viewHeight = newHeight;
  9.         mContext.getUITaskDispatcher().asyncDispatch(() -> updateVideoSize(videoScale));
  10.     }
  11. }

  12. private void updateVideoSize(double videoScale) {
  13.     if (videoScale > 1) {
  14.         surfaceView.setWidth(viewWidth);
  15.         surfaceView.setHeight((int) Math.min(viewWidth / videoScale, viewHeight));
  16.     } else {
  17.         surfaceView.setHeight(viewHeight);
  18.         surfaceView.setWidth((int) Math.min(viewHeight * videoScale, viewWidth));
  19.     }
  20. }
复制代码
PlayerView屏幕手势功能。
  1. gestureDetector = new GestureDetector(gestureView);
  2. surfaceView.setTouchEventListener((component, touchEvent) ->canGesture() && gestureDetector.onTouchEvent(touchEvent));
复制代码
编译运行该应用程序

应用启动后,视频文件将被打开并开始播放,持续播放到最后。效果如下图:

   

5. 创建视频控制业务逻辑      
上面的章节实现了视频播放的基本功能,本小节将创建一个控制器,包含基本的媒体控制UI元素如播放、暂停、恢复、重新加载按钮以及进度条。该控制器将与HmPlayer类一起提供一个基本功能全面且可操作的视频播放器。

创建SimpleVideoPlayerController

SimplePlayerController类为自定义组件,包括控制视频的播放、暂停、恢复以及进度条等控件。此处使用HarmonyOS EventHandler来进行UI更新,请参考HarmonyOS开发者文档线程间通信。
绑定HmPlayer。
  1. if (findComponentById(ResourceTable.Id_controller_view) instanceof SimplePlayerController) {
  2.     controllerView = (SimplePlayerController) findComponentById(ResourceTable.Id_controller_view);
  3. }
  4. controllerView.bind(player);
复制代码
其中initView方法初始化播放控制的控件。
  1. Component playerController = LayoutScatter.getInstance(context).parse(
  2.         ResourceTable.Layout_simple_player_controller_layout, null, false);
  3. addComponent(playerController);
  4. if (playerController.findComponentById(ResourceTable.Id_play_controller) instanceof Image) {
  5.     // 播放或者暂停按钮
  6.     playToogle = (Image) playerController.findComponentById(ResourceTable.Id_play_controller);
  7. }
  8. if (playerController.findComponentById(ResourceTable.Id_play_forward) instanceof Image) {
  9.     // 前进按钮
  10.     imageForward = (Image) playerController.findComponentById(ResourceTable.Id_play_forward);
  11. }
  12. if (playerController.findComponentById(ResourceTable.Id_play_backward) instanceof Image) {
  13.     // 后退按钮
  14.     imageBackward = (Image) playerController.findComponentById(ResourceTable.Id_play_backward);
  15. }
  16. if (playerController.findComponentById(ResourceTable.Id_progress) instanceof Slider) {
  17.     // 进度条
  18.     progressBar = (Slider) playerController.findComponentById(ResourceTable.Id_progress);
  19. }
复制代码
initListener方法是对HmPlayer和播放控制器相互之间状态变化的监听处理。

implPlayer.addPlayerStatusCallback(statusChangeListener);添加HmPlayer状态变化的监听,例如当视频播放完毕时,回调StatusChangeListener的statusCallback来刷新对控制器中各种组件的状态和显示值。HmPlayer中HmPlayerCallback中通过底层播放回调onPlayBackComplete来对界面视频状态进行更改。
  1. @Override
  2. public void onPlayBackComplete() {
  3.     for (StatusChangeListener callback : statusChangeCallbacks) {
  4.         status = PlayerStatus.COMPLETE;
  5.         callback.statusCallback(PlayerStatus.COMPLETE);
  6.     }
  7.     stop();
  8. }
复制代码
在SimplePlayerController的statusCallback中更新控制按钮状态。

  1. if (status == PlayerStatus.STOP || status == PlayerStatus.COMPLETE) {
  2.     controllerHandler.sendEvent(Constants.PLAYER_PROGRESS_RUNNING, EventHandler.Priority.IMMEDIATE);
  3.     playToogle.setPixelMap(ResourceTable.Media_ic_update);
  4.     progressBar.setEnabled(false);
  5. }
复制代码
此时播放按钮更新成待刷新图标,进度条不可拖拽。


创建PlayerLoading

在视频画面缓冲没有完成时,播放界面如果提供加载进度信息,用户体验更好。创建的PlayerLoading类设置一个布局并且添加StatusChangeListener监听回调,使得该控件可以根据状态显示或隐藏。
绑定HmPlayer。
  1. if (findComponentById(ResourceTable.Id_loading_view) instanceof PlayerLoading) {
  2.     loadingView = (PlayerLoading) findComponentById(ResourceTable.Id_loading_view);
  3. }
  4. loadingView.bind(player);
复制代码
设置Hmplayer播放器状态监听以更新UI。
  1. mPlayer.addPlayerStatuCallback(statu -> mContext.getUITaskDispatcher().asyncDispatch(() -> {
  2.     switch (statu) {
  3.         case PREPARING:
  4.         case BUFFERING:
  5.             show();
  6.             break;
  7.         case PLAY:
  8.             hide();
  9.             break;
  10.         default:
  11.             break;
  12.     }
  13. }));
复制代码
编译运行该应用程序

经过上面的步骤,此时运行程序就可以看到一个有前进、后退、播放、暂停的界面,用户可以自主控制该视频播放,效果如下图:

   

6. 恭喜你      
通过本篇Codelab你学到了:
  • HarmonyOS中一个完整的视频播放应用需包括UI、Surface和媒体播放器。
  • 使用player.setSource(source)指定视频文件的路径。
  • 使用SurfaceOps.Callback来处理surface创建、状态改变和销毁的回调。
  • 创建内部类HmPlayerCallback实现Player.IPlayerCallback的接口,监听视频状态改变,添加对控制器组件状态和缓冲界面的回调方法。
  • 创建HmPlayerLifeCycle来管理HmPlayer生命周期。
   

7. 参考         

回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友