在鴻蒙上實現本地和Internet視頻資源播放
1. 介紹
本篇Codelab將實現的內容
本篇Codelab旨在讓開發者了解手機HarmonyOS應用開發,常用布局、典型控件、FA組件、媒體-視頻、跨設備協同的體驗以及從工程創建到代碼和布局的編寫,再到編譯構建、部署和運行全過程。
您將構建一個基于HarmonyOS Player類實現的應用程序,該應用程序功能為播放本地視頻資源或從Internet獲得的視頻資源。效果圖如下:
您將會學到什么
● 如何使用Player類播放視頻
● 如何使用自定義控件來控制視頻播放
● 如何添加并使用媒體事件的事件偵聽器和回調
硬件要求
● 操作系統:Windows10 64位
● 內存:8GB及以上
● 硬盤:100GB及以上
● 分辨率:1280*800像素及以上
軟件要求
● 安裝Huawei DevEco Studio,詳情請參考下載和安裝軟件
● 設置Huawei DevEco Studio開發環境,Huawei DevEco Studio開發環境需要依賴于網絡環境,需要連接上網絡才能確保工具的正常使用,可以根據如下兩種情況來配置開發環境
1.如果可以直接訪問Internet,只需進行下載HarmonyOS SDK操作
2.如果網絡不能直接訪問Internet,需要通過代理服務器才可以訪問,請參考配置開發環境
說明
如需要在手機中運行程序,則需要提前申請證書,如使用模擬器可忽略
● 生成秘鑰和申請證書,詳情請參考準備簽名文件
技能要求
● 具備DevEco Studio中創建、構建和運行應用經驗
● 熟悉Ability和AbilitySlice生命周期及使用PA/FA的能力
2. 代碼結構
本篇Codelab只對核心代碼進行講解,對于完整代碼,我們在參考提供下載方式。接下來我們會講解整個工程的代碼結構,如下圖:
● 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">
- <DependentLayout
- ohos:id="$+id:parent_layout"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:alignment="center"
- ohos:background_element="#ffffff"/>
- </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);
- // 在Constants中定義視頻播放的起始位置
- startMillisecond = intent.getIntParam(Constants.INTENT_STARTTIME_PARAM, 0);
- // 初始化surface布局
- initView();
- player.getLifecycle().onStart();
- }
將預置的視頻資源初始化為url對象,并通過initView方法對視頻播放的控件進行初始化及賦值。
- private String url = "entry/resources/base/media/gubeishuizhen.mp4";
- private void initView() {
- DependentLayout playerLayout = (DependentLayout) findComponentById(ResourceTable.Id_parent_layout);
- player = new HmPlayer.Builder(this).setStartMillisecond(mStartMillisecond).setFilePath(url).create();
- playerLayout.addComponent(player.getPlayerView());
- player.play();
- }
—-結束
創建HmPlayer
HmPlayer類是繼承自對HarmonyOS Player封裝的ImPlayer。如果您還不了解HarmonyOS Player,請參考視頻播放開發指導。
需要注意的是當頁面初始化Player類執行play方法時,視頻并沒有出現畫面。圖像渲染在屏幕上需要使用SurfaceProvider,該類控制surface的尺寸和格式,修改surface的像素,監視surface的變化等等。當底層顯示系統第一次創建surface之后會調用surfaceCreated(SurfaceOps surfaceOps)回調函數。HmPlayer中通過設置回調增加對視頻的播放開始或停止控制。
- private SurfaceOps.Callback surfaceCallback = new SurfaceOps.Callback() {
- @Override
- public void surfaceCreated(SurfaceOps surfaceOps) {
- // 標記surfaceView狀態
- isSurfaceViewCreated = true;
- surface = surfaceOps.getSurface();
- start();
- }
- @Override
- public void surfaceChanged(SurfaceOps surfaceOps, int i, int width, int height) {
- LogUtil.info(TAG, "surfaceChanged i is " + i + ",width is " + width + ",height is " + height);
- }
- @Override
- public void surfaceDestroyed(SurfaceOps surfaceOps) {
- LogUtil.info(TAG, "surfaceDestroyed");
- isSurfaceViewCreated = false;
- }
- };
surfaceView的初始化在HmPlayer構造函數中:
- private HmPlayer(Builder builder) {
- ...
- surfaceView = new SurfaceProvider(playerBuilder.mContext);
- DependentLayout.LayoutConfig layoutConfig = new DependentLayout.LayoutConfig();
- layoutConfig.addRule(DependentLayout.LayoutConfig.CENTER_IN_PARENT);
- // 設置surfaceView布局
- surfaceView.setLayoutConfig(layoutConfig);
- surfaceView.setVisibility(Component.VISIBLE);
- surfaceView.setFocusable(Component.FOCUS_ENABLE);
- surfaceView.setTouchFocusable(true);
- surfaceView.requestFocus();
- // 設置surfaceView是否在最上方
- surfaceView.pinToZTop(playerBuilder.isTopPlay);
- surfaceView.getSurfaceOps().get().addCallback(surfaceCallback);
- }
在執行surfaceCreated回調時會執行HarmonyOS中Player的play方法。
- private void start() {
- if (isSurfaceViewCreated) {
- threadPoolExecutor.execute(() -> {
- player.setVideoSurface(surface);
- player.prepare();
- if (playerBuilder.startMillisecond > 0) {
- int microsecond = playerBuilder.startMillisecond * MICRO_MILLI_RATE;
- player.rewindTo(microsecond);
- } else {
- player.play();
- }
- });
- }
- }
編譯運行該應用程序
應用啟動后,視頻文件將被打開并開始播放,持續播放到最后。效果如下圖:
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);
- }
- });
- }
編譯運行該應用程序
經過上面的步驟,此時運行程序就可以看到一個有前進、后退、播放、暫停的界面,用戶可以自主控制該視頻播放,效果如下圖:
5. 恭喜你
通過本篇Codelab你學到了:
● HarmonyOS中一個完整的視頻播放應用需包括UI、Surface和媒體播放器。
● 使用player.setSource(source)指定視頻文件的路徑。
● 使用SurfaceOps.Callback來處理surface創建、狀態改變和銷毀的回調。
● 創建內部類HmPlayerCallback實現Player.IPlayerCallback的接口,監聽視頻狀態改變,添加對控制器組件狀態和緩沖界面的回調方法。
● 創建HmPlayerLifeCycle來管理HmPlayer生命周期。
6. 參考