HarmonyOS 制作简易视频播放器过程详解

奶盖
发布于 2021-7-20 14:29
浏览
2收藏

1. 介绍

 

播放视频的多媒体应用程序通常包含两个部分:

1.给定媒体源的播放器加载媒体资源,并通过Surface来进行画面渲染,将其呈现为视频。
2.具有传输控件的用户界面(UI),以承载播放器并显示播放器的状态。
本应用程序原理图如下图:

HarmonyOS 制作简易视频播放器过程详解-鸿蒙开发者社区

本篇Codelab将实现的内容

 

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

 

 

 

        您将构建一个基于HarmonyOS

Player

      类实现的应用程序,该应用程序功能为播放本地视频资源或从Internet获得的视频资源。效果图如下:

HarmonyOS 制作简易视频播放器过程详解-鸿蒙开发者社区

您将会学到什么

 

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

 

硬件要求

 

  • 操作系统:Windows10 64位
  • 内存:8GB及以上
  • 硬盘:100GB及以上
  • 分辨率:1280*800像素及以上

 

软件要求

 

  • 安装DevEco Studio,详情请参考DevEco Studio下载
  • 设置Huawei DevEco Studio开发环境,Huawei DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境
    1. 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作
    2. 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境
    3.  说明:
      如需要在手机中运行程序,则需要提前申请证书,如使用模拟器可忽略
    4.  

 

技能要求

 

  • 具备DevEco Studio中创建、构建和运行应用经验
  • 熟悉Ability和AbilitySlice生命周期及使用PA/FA的能力

     

     

 

 

2. 代码结构

 

 

本篇Codelab只对核心代码进行讲解,对于完整代码,我们在参考提供下载方式。接下来我们会讲解整个工程的代码结构,如下图:HarmonyOS 制作简易视频播放器过程详解-鸿蒙开发者社区

  • api:视频播放状态改变及屏幕状态变化监听。
  • constant:定义视频状态、进度条和控制器状态。
  • factoty:创建SourceFactory类来根据视频来源创建视频源。
  • manager:创建HmPlayerLifecycle来处理Player类的生命周期。
  • view:创建PlayerLoading、SimplePlayerController类分别为视频加载状态及进度条控制类文件。
  • HmPlayer:封装播放器的主要功能方法。
  • slice:创建MainAbilitySlice、SimplePlayerAbilitySlice分别为进入应用的主程序页面和视频播放页面。
  • utils:存放所有封装好的公共方法,如DateUtils,LogUtils等。
  • resources:存放工程使用到的资源文件,其中resources\base\layout下存放xml布局文件;resources\base\media下存放视频文件。
  • config.json:Ability声明及权限配置。

 

3. 创建视频播放业务逻辑

 

该应用程序可播放的视频格式包括mp4、mov、3gp、mkv,首先准备一份视频文件并复制到"resources/base/layout/media"文件目录。下面将会介绍视频列表布局及播放逻辑。

 

创建视频播放页面文件及布局

 

Step 1 - 创建simple_video_play_layout.xml布局文件展示视频列表。

<DependentLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:id="$+id:parent"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:background_element="#00000000"
    ohos:alignment="center">

    <com.huawei.codelab.player.view.PlayerView
        ohos:id="$+id:player_view"
        ohos:height="match_parent"
        ohos:width="match_parent"/>

    <com.huawei.codelab.player.view.PlayerLoading
        ohos:id="$+id:loading_view"
        ohos:height="match_parent"
        ohos:width="match_parent"/>

    <com.huawei.codelab.player.view.SimplePlayerController
        ohos:id="$+id:controller_view"
        ohos:height="match_parent"
        ohos:width="match_parent"/>

</DependentLayout>

 

该布局文件有两个id,parent是整个播放页面的布局id,parent_layout是视频画面的布局id。

 

Step 2 - 创建SimplePlayerAbilitySlice类,初次创建该页面进行初始化

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    super.setUIContent(ResourceTable.Layout_simple_video_play_layout);
    player = new HmPlayer.Builder(this).setFilePath(url).create();
    player.getLifecycle().onStart();
    initComponent();
}

 

将预置的视频资源初始化为url对象,并通过initView方法对视频播放的控件进行初始化及赋值。

private void initComponent() {
    if (findComponentById(ResourceTable.Id_parent) instanceof DependentLayout) {
        parentLayout = (DependentLayout) findComponentById(ResourceTable.Id_parent);
    }
    if (findComponentById(ResourceTable.Id_player_view) instanceof PlayerView) {
        playerView = (PlayerView) findComponentById(ResourceTable.Id_player_view);
    }
    if (findComponentById(ResourceTable.Id_loading_view) instanceof PlayerLoading) {
        loadingView = (PlayerLoading) findComponentById(ResourceTable.Id_loading_view);
    }
    if (findComponentById(ResourceTable.Id_controller_view) instanceof SimplePlayerController) {
        controllerView = (SimplePlayerController) findComponentById(ResourceTable.Id_controller_view);
    }
    playerView.bind(player);
    loadingView.bind(player);
    controllerView.bind(player);
}

 

—-结束

创建HmPlayer

 

HmPlayer类是以HarmonyOS Player为基础封装了复杂播放流程并对外开放ImPlayer接口类以实现所有支持的功能。

 

/** 
 * IPlayer interface 
 * 
 * @since 2021-04-04 
 */ 
public interface ImplPlayer { 
    /** 
     * addSurface 
     * 
     * @param surface surface 
     */ 
    void addSurface(Surface surface); 
 
    /** 
     * addPlayerStatuCallback 
     * 
     * @param callback callback 
     */ 
    void addPlayerStatuCallback(StatuChangeListener callback); 
 
    /** 
     * removePlayerStatuCallback 
     * 
     * @param callback callback 
     */ 
    void removePlayerStatuCallback(StatuChangeListener callback); 
 
    /** 
     * addPlayerViewCallback 
     * 
     * @param callback callback 
     */ 
    void addPlayerViewCallback(ScreenChangeListener callback); 
 
    /** 
     * removePlayerViewCallback 
     * 
     * @param callback callback 
     */ 
    void removePlayerViewCallback(ScreenChangeListener callback); 
 
    /** 
     * play 
     */ 
    void play(); 
 
    /** 
     * replay 
     */ 
    void replay(); 
 
    /** 
     * reload 
     * 
     * @param filepath filepath 
     * @param startMillisecond startMillisecond 
     */ 
    void reload(String filepath, int startMillisecond); 
 
    /** 
     * resume 
     */ 
    void resume(); 
 
    /** 
     * pause 
     */ 
    void pause(); 
 
    /** 
     * getCurrentPosition 
     * 
     * @return current position 
     */ 
    int getCurrentPosition(); 
 
    /** 
     * getDuration 
     * 
     * @return duration 
     */ 
    int getDuration(); 
 
    /** 
     * getVolume 
     * 
     * @return float 
     */ 
    float getVolume(); 
 
    /** 
     * set play volume 
     * 
     * @param volume 0~1 
     */ 
    void setVolume(float volume); 
 
    /** 
     * set play speed 
     * 
     * @param speed 0~12 
     */ 
    void setPlaySpeed(float speed); 
 
    /** 
     * getVideoScale 
     * 
     * @return double 
     */ 
    double getVideoScale(); 
 
    /** 
     * rewindTo 
     * 
     * @param startMicrosecond startMicrosecond(ms) 
     */ 
    void rewindTo(int startMicrosecond); 
 
    /** 
     * isPlaying 
     * 
     * @return isPlaying 
     */ 
    boolean isPlaying(); 
 
    /** 
     * stop 
     */ 
    void stop(); 
 
    /** 
     * release 
     */ 
    void release(); 
 
    /** 
     * getLifecycle 
     * 
     * @return ImplLifecycle 
     */ 
    ImplLifecycle getLifecycle(); 
 
    /** 
     * getBuilder 
     * 
     * @return Builder 
     */ 
    HmPlayer.Builder getBuilder(); 
 
    /** 
     * getPlayerStatu 
     * 
     * @return PlayerStatu 
     */ 
    PlayerStatu getPlayerStatu(); 
 
    /** 
     * resizeScreen 
     * 
     * @param width width 
     * @param height height 
     */ 
    void resizeScreen(int width, int height); 
 
    /** 
     * openGesture 
     * 
     * @param isOpen isOpen 
     */ 
    void openGesture(boolean isOpen); 
 
    /** 
     * openGesture 
     * 
     * @return isGestureOpen 
     */ 
    boolean isGestureOpen(); 
}

通过HmPlayer.Builder构造器设置播放源、开始时间等播放参数并初始化HmPlayer。

player = new HmPlayer.Builder(this).setFilePath(url).create();

添加播放器状态、UI变化监听,在监听中处理逻辑。

player.addPlayerStatuCallback(statu -> mContext.getUITaskDispatcher().asyncDispatch(() -> { 
    switch (statu) { 
        case PREPARING: 
        case BUFFERING: 
            show(); 
            break; 
        case PLAY: 
            hide(); 
            break; 
        default: 
            break; 
    } 
})); 
 
player.addPlayerViewCallback((width, height) -> mContext.getUITaskDispatcher().asyncDispatch(() -> { 
    if (width > 0) { 
        setWidth(width); 
    } 
    if (height > 0) { 
        setHeight(height); 
    } 
}));

设置播放器生命周期与slice生命周期一致。

@Override 
public void onStart(Intent intent) { 
    super.onStart(intent); 
        ... 
    player.getLifecycle().onStart(); 
} 
 
@Override 
public void onForeground(Intent intent) { 
    player.getLifecycle().onForeground(); 
    super.onForeground(intent); 
} 
 
@Override 
protected void onBackground() { 
    player.getLifecycle().onBackground(); 
    super.onBackground(); 
} 
 
@Override 
protected void onStop() { 
        ... 
    player.getLifecycle().onStop(); 
    super.onStop(); 
}

如果您还不了解HarmonyOS Player,请参考视频播放开发指导

 

创建PlayerView
图像渲染在屏幕上需要使用SurfaceProvider,该类控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等。当底层显示系统第一次创建surface之后会调用surfaceCreated(SurfaceOps surfaceOps)回调函数。

 surfaceView.getSurfaceOps().ifPresent(surfaceOps -> surfaceOps.addCallback(new SurfaceOps.Callback() {
            @Override
            public void surfaceCreated(SurfaceOps surfaceOps) {
                surface = surfaceOps.getSurface();
                if (player != null) {
                    player.addSurface(surface);
                }
            }

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

            @Override
            public void surfaceDestroyed(SurfaceOps surfaceOps) {
            }
        }));

PlayerView绑定HmPlayer:

if (findComponentById(ResourceTable.Id_player_view) instanceof PlayerView) { 
    playerView = (PlayerView) findComponentById(ResourceTable.Id_player_view); 
} 
playerView.bind(player);

视频尺寸与播放框架尺寸适配。

@Override 
public void onRefreshed(Component component) { 
    int newWidth = component.getWidth(); 
    int newHeight = component.getHeight(); 
    double videoScale = player.getVideoScale(); 
    if (videoScale != Constants.NUMBER_NEGATIVE_1 && (newWidth != viewWidth || newHeight != viewHeight)) { 
        viewWidth = newWidth; 
        viewHeight = newHeight; 
        mContext.getUITaskDispatcher().asyncDispatch(() -> updateVideoSize(videoScale)); 
    } 
} 
 
private void updateVideoSize(double videoScale) { 
    if (videoScale > 1) { 
        surfaceView.setWidth(viewWidth); 
        surfaceView.setHeight((int) Math.min(viewWidth / videoScale, viewHeight)); 
    } else { 
        surfaceView.setHeight(viewHeight); 
        surfaceView.setWidth((int) Math.min(viewHeight * videoScale, viewWidth)); 
    } 
}

PlayerView屏幕手势功能。

gestureDetector = new GestureDetector(gestureView); 
surfaceView.setTouchEventListener((component, touchEvent) ->canGesture() && gestureDetector.onTouchEvent(touchEvent));

 

编译运行该应用程序

 

应用启动后,视频文件将被打开并开始播放,持续播放到最后。效果如下图:HarmonyOS 制作简易视频播放器过程详解-鸿蒙开发者社区

 

4. 创建视频控制业务逻辑

 

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

 

创建SimpleVideoPlayerController

 

        SimplePlayerController类为自定义组件,包括控制视频的播放、暂停、恢复以及进度条等控件。此处使用HarmonyOS EventHandler来进行UI更新,请参考HarmonyOS开发者文档

线程间通信

 

public SimplePlayerController(Context context, ImplPlayer player) { 
    super(context); 
    this.context = context; 
    implPlayer = player; 
    // 创建子线程给自己发消息来及时更新UI 
    createHandler(); 
    initView(); 
    initListener(); 
}

 

其中initView方法初始化播放控制的控件。

Component playerController = LayoutScatter.getInstance(context).parse( 
        ResourceTable.Layout_simple_player_controller_layout, null, false); 
addComponent(playerController); 
if (playerController.findComponentById(ResourceTable.Id_play_controller) instanceof Image) { 
    // 播放或者暂停按钮 
    playToogle = (Image) playerController.findComponentById(ResourceTable.Id_play_controller); 
} 
if (playerController.findComponentById(ResourceTable.Id_play_forward) instanceof Image) { 
    // 前进按钮 
    imageForward = (Image) playerController.findComponentById(ResourceTable.Id_play_forward); 
} 
if (playerController.findComponentById(ResourceTable.Id_play_backward) instanceof Image) { 
    // 后退按钮 
    imageBackward = (Image) playerController.findComponentById(ResourceTable.Id_play_backward); 
} 
if (playerController.findComponentById(ResourceTable.Id_progress) instanceof Slider) { 
    // 进度条 
    progressBar = (Slider) playerController.findComponentById(ResourceTable.Id_progress); 
}

 

initListener方法是对HmPlayer和播放控制器相互之间状态变化的监听处理。

implPlayer.addPlayerStatusCallback(statusChangeListener);

 

添加HmPlayer状态变化的监听,例如当视频播放完毕时,回调StatusChangeListener的statusCallback来刷新对控制器中各种组件的状态和显示值。HmPlayer中HmPlayerCallback中通过底层播放回调onPlayBackComplete来对界面视频状态进行更改。

@Override 
public void onPlayBackComplete() { 
    for (StatusChangeListener callback : statusChangeCallbacks) { 
        status = PlayerStatus.COMPLETE; 
        callback.statusCallback(PlayerStatus.COMPLETE); 
    } 
    stop(); 
}

 

在SimplePlayerController的statusCallback中更新控制按钮状态

if (status == PlayerStatus.STOP || status == PlayerStatus.COMPLETE) { 
    controllerHandler.sendEvent(Constants.PLAYER_PROGRESS_RUNNING, EventHandler.Priority.IMMEDIATE); 
    playToogle.setPixelMap(ResourceTable.Media_ic_update); 
    progressBar.setEnabled(false); 
}

此时播放按钮更新成待刷新图标,进度条不可拖拽。

 

创建PlayerLoading

 

在视频画面缓冲没有完成时,播放界面如果提供加载进度信息,用户体验更好。创建的PlayerLoading类设置一个布局并且添加StatusChangeListener监听回调,使得该控件可以根据状态显示或隐藏。

public PlayerLoading(Context context, ImplPlayer player) { 
    super(context); 
    this.player = player; 
    initView(context); 
    initListener(); 
} 
private void initListener() { 
    player.addPlayerStatusCallback(new StatusChangeListener() { 
        @Override 
        public void statusCallback(PlayerStatus status) { 
            //获取主线程更新UI 
            mContext.getUITaskDispatcher().delayDispatch( 
                    new Runnable() { 
                        @Override 
                        public void run() { 
                            if (status == PlayerStatus.PREPARING || status == PlayerStatus.BUFFERING) { 
                                show(); 
                            } else if (status == PlayerStatus.PLAY) { 
                                hide(); 
                            } else { 
                                LogUtil.info(PlayerLoading.class.getName(), "statuCallback else message"); 
                            } 
                        } 
                    }, 0); 
        } 
    }); 
}

 

编译运行该应用程序

 

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

HarmonyOS 制作简易视频播放器过程详解-鸿蒙开发者社区

 

5. 恭喜你

 

通过本篇Codelab你学到了:

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

 

6. 参考

 

gitee源码

github源码

已于2021-7-20 14:29:26修改
2
收藏 2
回复
举报
回复
    相关推荐