首页 > 其他分享 >鸿蒙应用开发:实现简单的媒体播放器

鸿蒙应用开发:实现简单的媒体播放器

时间:2023-11-01 14:22:24浏览次数:112  
标签:媒体播放器 鸿蒙 void videoPlayer 应用 Override new null public

 距离 Harmony OS 发布已过去了一段时间,为了了解鸿蒙系统的功能与特性,今天我们将准备使用系统 API 实现一个简单的媒体播放器 demo。

大家在阅读本文后会对媒体播放器相关的 API 有一定的了解,并且可以根据文中的步骤一起动手实操,实现在鸿蒙系统上的简单媒体播放器!

VideoPlayerDemo 仓库地址:VideoPlayerDemo: 鸿蒙系统 API 实现简单媒体播放器

话不多说,下面我将带领大家一起看一下媒体播放器的实现流程。

一、媒体资源文件的权限申请

Harmony OS 中所有的应用均在应用沙盒内运行。默认情况下,应用只能访问有限的系统资源,系统负责管理应用对资源的访问权限。

所以要实现媒体播放器功能当然需要访问媒体资源,这时就需要向操作系统申请权限:

1、需要在 config.json 中的 ""reqPermissions" 添加配置:

"reqPermissions": [
  {
    "name": "ohos.permission.READ_USER_STORAGE"
  }
]

2、需要在程序启动时请求读取本地媒体资源的权限:

if (verifyCallingOrSelfPermission(SystemPermission.READ_USER_STORAGE) != IBundleManager.PERMISSION_GRANTED) {
    requestPermissionsFromUser(new String[]{SystemPermission.READ_USER_STORAGE}, REQUEST_CODE);
}

二、获取本地的媒体资源文件

在申请到媒体资源的权限后,我们就可以去获取本地的媒体资源信息,包括音频及视频。

我们可以通过以下操作,将所有的音视频文件标识为 AVElement 并存入 List<AVElement> 中方便我们做 UI 展示,稍后可通过 AVElement 获取媒体资源文件路径,传递给系统播放器。

private final List<AVElement> avElements = new ArrayList<>();


public VideoElementManager(Context context) {
    loadAudioFromMediaLibrary(context);
    loadVideoFromMediaLibrary(context);
}


// 查找音频
private void loadAudioFromMediaLibrary(Context context) {
    Uri audioUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;
    DataAbilityHelper helper = DataAbilityHelper.creator(context, audioUri, false);
    try {
        ResultSet resultSet = helper.query(audioUri, null, null);
        LogUtil.info(TAG, "The audio result size: " + resultSet.getRowCount());
        processResult(resultSet);
        resultSet.close();
    } catch (DataAbilityRemoteException e) {
        LogUtil.error(TAG, "Query system media failed.");
    } finally {
        helper.release();
    }
}


// 查找视频
private void loadVideoFromMediaLibrary(Context context) {
    Uri videoUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
    DataAbilityHelper helper = DataAbilityHelper.creator(context, videoUri, false);
    try {
        ResultSet resultSet = helper.query(videoUri, null, null);
        LogUtil.info(TAG, "The video result size: " + resultSet.getRowCount());
        processResult(resultSet);
        resultSet.close();
    } catch (DataAbilityRemoteException e) {
        LogUtil.error(TAG, "Query system media failed.");
    } finally {
        helper.release();
    }
}


// 处理数据到 List<AVElement>
private void processResult(ResultSet resultSet) {
    while (resultSet.goToNextRow()) {
        String path = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DATA));
        String title = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.TITLE));
        AVDescription bean =
                new AVDescription.Builder().setTitle(title).setIMediaUri(Uri.parse(path)).setMediaId(path).build();
        avElements.add(new AVElement(bean, AVElement.AVELEMENT_FLAG_PLAYABLE));
    }
}

 

三、简单的播放功能实现

通过以上的步骤,我们已经获取了媒体资源权限,并且获取到本地的所有音视频,下面我们将通过调用系统 API 实现简单的播放功能。

下图为媒体播放器界面:


 通过上面的 UI 界面,我们可以清晰的看出要实现哪些简单功能,例如播放、暂停、恢复、跳转、音量调节、倍速播放和播放进度更新。

下面将给大家详细介绍以上这些功能的具体实现方法。

1、渲染 View 准备

需要播放媒体资源,特别是视频资源,我们当然需要有 View 去渲染,鸿蒙系统 API 需要设置的 View 是 Surface,我们通过以下方法获取 Surface。

// 声明 SurfaceProvider, Surface
private SurfaceProvider surfaceProvider;
private DirectionalLayout playViewLayout;
private Surface surface;

private void addSurfaceProvider() {
    surfaceProvider = new SurfaceProvider(this);

    if (surfaceProvider.getSurfaceOps().isPresent()) {
        surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
        surfaceProvider.pinToZTop(true);
    }
}

// 生成 Surface 的回调
class SurfaceCallBack implements SurfaceOps.Callback {
    @Override
    public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
        if (surfaceProvider.getSurfaceOps().isPresent()) {
            surface = surfaceProvider.getSurfaceOps().get().getSurface();
            LogUtil.info(TAG, "surface set");
        }
    }

    @Override
    public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {
        LogUtil.info(TAG, "surface changed");
    }

    @Override
    public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {
        LogUtil.info(TAG, "surface destroyed");
    }
}

 

playViewLayout = (DirectionalLayout) findComponentById(ResourceTable.Id_playViewLayout);
playViewLayout.addComponent(surfaceProvider);

2、播放

我们获取到 Surface 后就可以调用系统 API 进行播放了。以下是对系统 API 的简单封装,我们可以调用 play 接口播放媒体资源。

private Player videoPlayer;
private Runnable videoRunnable;

public synchronized void play(AVElement avElement, Surface surface) {
    if (videoPlayer != null) {
        // 关闭当前播放资源
        // release 会重置播放状态并关闭定时器
        videoPlayer.stop();
        videoPlayer.release();
        videoPlayer = null;
    }

    if (videoRunnable != null) {
        ThreadPoolManager.getInstance().cancel(videoRunnable);
    }

    videoPlayer = new Player(context);
    setPlayerCallback();

    videoRunnable = () -> playInner(avElement, surface);
    ThreadPoolManager.getInstance().execute(videoRunnable);
    LogUtil.info(TAG, "play");
}

private void playInner(AVElement avElement, Surface surface) {
    Source source = new Source(avElement.getAVDescription().getMediaUri().toString());
    videoPlayer.setSource(source);
    videoPlayer.setVideoSurface(surface);
    LogUtil.info(TAG, source.getUri());

    videoPlayer.prepare();
    videoPlayer.play();
    playbackState = PlaybackState.VIDEO_PLAYER_PLAYING;
    startTimer();
}

// 设置 Callback
private void setPlayerCallback() {
    videoPlayer.setPlayerCallback(new Player.IPlayerCallback() {
        @Override
        public void onPrepared() {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onPrepared();
                }
            });
        }

        @Override
        public void onMessage(int type, int extra) {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onMessage(type, extra);
                }
            });
        }

        @Override
        public void one rror(int errorType, int errorCode) {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onError(errorType, errorCode);
                }
            });
        }

        @Override
        public void onResolutionChanged(int width, int height) {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onResolutionChanged(width, height);
                }
            });
        }

        @Override
        public void onPlayBackComplete() {
            playbackState = PlaybackState.VIDEO_PLAYER_PLAY_ENDED;
            endTimer();
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onPlayBackComplete();
                }
            });
        }

        @Override
        public void onRewindToComplete() {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onRewindToComplete();
                }
            });
        }

        @Override
        public void onBufferingChange(int percent) {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onBufferingChange(percent);
                }
            });
        }

        @Override
        public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onNewTimedMetaData(mediaTimedMetaData);
                }
            });
        }

        @Override
        public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
            if (eventHandler == null) {
                return;
            }
            EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
            handler.postTask(new Runnable() {
                @Override
                public void run() {
                    eventHandler.onMediaTimeIncontinuity(mediaTimeInfo);
                }
            });
        }
    });
}

3、暂停、恢复和跳转

实现一个媒体播放器,我们当然是需要暂停、恢复和跳转功能,以下是简单的封装。

这里有一个问题大家需要注意,虽然系统 API 中 getCurrentTime 接口返回值单位是毫秒,但是跳转接口 rewindTo 的接口参数单位是微秒。

 

// 暂停
public synchronized void pause() {
    if (videoPlayer == null) {
        return;
    }
    videoPlayer.pause();
    playbackState = PlaybackState.VIDEO_PLAYER_PAUSING;
    endTimer();
    LogUtil.info(TAG, "pause");
}

// 恢复
public synchronized void resume() {
    if (videoPlayer == null) {
        return;
    }
    videoPlayer.play();
    playbackState = PlaybackState.VIDEO_PLAYER_PLAYING;
    startTimer();
    LogUtil.info(TAG, "resume");
}

// 跳转
public synchronized void seekTo(long millisecond) {
    if (videoPlayer == null) {
        return;
    }

    // 注意,rewindTo 接口参数单位是微秒
    videoPlayer.rewindTo(millisecond * 1000);
    LogUtil.info(TAG, "seek" + videoPlayer.getCurrentTime());
}

4、时长获取和播放进度显示

我们对获取时长接口做了简单的封装,但是系统 API 中没有相关的播放进度回调,为了进行 UI 展示,我们需要自己维护一个播放状态和定时器去定时获取当前播放进度。

// 获取资源总时长,需要收到 onPrepared 回调后调用
public synchronized int getDuration() {
    if (videoPlayer == null) {
        return 0;
    }

    return videoPlayer.getDuration();
}

private Timer timer;
private PlaybackState playbackState;

private void startTimer() {
    if (timer == null) {
        timer = new Timer("PlaybackPorgressTimer");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                playbackProgressUpdate();
            }
        }, 200L, 200L);
    }
}

private void endTimer() {
    if (timer == null) {
        return;
    }
    timer.cancel();
    timer = null;
}

private void playbackProgressUpdate() {
    if (eventHandler == null || videoPlayer == null) {
        return;
    }
    int currentPlaybackProgress = videoPlayer.getCurrentTime();
    EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
    handler.postTask(new Runnable() {
        @Override
        public void run() {
            eventHandler.onPlaybackProgressUpdate(currentPlaybackProgress);
        }
    });
}

5、音量调节和倍速播放

鸿蒙播放器系统 API 同样暴露了音量调节和倍速播放的接口,不过需要注意的是音量调节的系统 API 参数范围是 0.0~1.0,改接口仅控制播放器的音量,与手机侧边音量控制无关,有兴趣的话可以自己测试一下。

public synchronized void setPlaybackVolume(int volume) {
    if (videoPlayer == null) {
        return;
    }

    videoPlayer.setVolume((float) ((float) volume/100.0));
}

public synchronized void setPlaybackSpeed(float speed) {
    if (videoPlayer == null) {
        return;
    }

    videoPlayer.setPlaybackSpeed(speed);
}

public synchronized float getPlaybackSpeed() {
    if (videoPlayer == null) {
        return 0.0f;
    }

    return videoPlayer.getPlaybackSpeed();
}

四、总结

通过调用系统 API 实现一个简单的媒体播放器,相信大家一定对 Harmony OS都有了初步的了解与认识,比如说真正意义上的跑起来一个鸿蒙 APP,中间经过了开发者账号的申请、APP 证书的申请等等。

在11月底,ZEGO 即构科技首发适配鸿蒙系统的 Express SDK 1.0 版本,并正式启动了公测,这其中包括摄像头、麦克风、扬声器等设备的深度兼容与适配,实现了更加稳定、快速的设备管理能力,感兴趣可点击链接了解:ZEGO 即构科技首发适配鸿蒙系统的 Express SDK 1.0 版本,并正式启动公测!

 

 


标签:媒体播放器,鸿蒙,void,videoPlayer,应用,Override,new,null,public
From: https://www.cnblogs.com/zegoinfo/p/17803018.html

相关文章

  • 如何找到 SAP Fiori Elements 应用某个字段显示值具体的数据源试读版
    笔者将自己在SAP领域16年(2007~2023)的SAPUI5(Fiori)和OData开发的技术沉淀,进行了系统的归纳和总结,分别写成了三套由浅入深的学习教程,收到了不错的反响:零基础快速学习ABAP一套适合SAPUI5开发人员循序渐进的学习教程SAPOData开发实战教程-从入门到提高这三套教程都......
  • SAP Fiori Elements 应用里的 ui5.yaml 文件详解试读版
    本教程第4篇文章,我们介绍了本地启动SAPFioriElements应用的三种模式。4.SAPFioriElements本地应用启动的三种模式辨析以默认方式即命令行yarnstart启动之后,项目文件夹里的ui5.yaml文件会默认被加载并解析。ui5.yaml这个文件,在本地开发FreestyleUI5时也会遇到,笔......
  • SAP Fiori Elements 应用里的 ui5-local.yaml
    SAPFioriElements是SAP的一种开发框架,用于快速构建SAPFiori应用程序。SAPFioriElements应用程序的一个重要方面是模拟服务器(mockserver),它允许在本地开发环境中模拟OData服务,以便在没有真实后端服务的情况下进行开发和测试。ui5-local.yaml文件用于配置本地开发环境,......
  • ToF传感器在IoT产品上的几个应用
    ToF(Time-of-Flight飞行时间)传感器是一种基于飞行时间来计算距离的传感器,ToF是一种测距方法,可以通过ToF传感器得到目标距离(depth)信息和整个场景的灰度图像。ToF传感器在需要高精度距离测量和深度感知领域应用具有较大的优势。TOF 技术已经被广泛应用于多种设备,包括智能手机、平板......
  • vue2 单页面应用兼容 ie & 对象不支持"append"属性或方法
    在平时如果用到vue2但是打开ie测试的时候发现代码没起到预期的效果(作用)的话。就要用到polyfill和browser来对你的vue代码转成es5语法啦。具体用法是:先下载polyfill和browser的js文件然后引入polyfill和browser的js文件1<scriptsrc="./graphic_js/pol......
  • 实验3_c语言函数应用编程
    task1#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_lines(intn);intmain(){intline,col......
  • R语言有限混合模型聚类FMM、广义线性回归模型GLM混合应用分析威士忌市场和研究专利申
    最近我们被客户要求撰写关于有限混合模型聚类FMM的研究报告,包括一些图形和统计输出。摘要有限混合模型是对未观察到的异质性建模或近似一般分布函数的流行方法。它们应用于许多不同的领域,例如天文学、生物学、医学或营销。本文给出了这些模型的概述以及许多应用示例。介绍有限混合......
  • 【Azure App Service】为部署在App Service上的PHP应用开启JIT编译器
    问题描述在AppServiceforlinux上创建一个PHP应用,通过phpinfo()查看PHP的扩展设置,发现JIT没有被开启, jit_buffer_size大小为0.那么,在AppService的环境中,如何开启JIT呢? 问题解答PHP8在PHP的内核中添加了JIT编译器,可以极大地提高性能。首先,仅在启用opcache的情况下,JIT才有效......
  • 【那些遇到的认知问题】如何同时运行 2 个 CUDA 应用程序?
    前言PC只有一个Nvidia显卡,程序A正在运行,训练分类,显卡内存占用不到50%,如果想同时运行另一个训练语义分割的程序B,是可行的嘛?结论理论上,如果对CUDA和GPU编程熟悉,可以对内核应用程序进行序列化,使得一个应用程序的内核正在运行时,GPU不会调度另一个应用程序的内核,类似于多进程。否则,如果......
  • 多通道振弦数据记录仪在铁路隧道监测中的重要应用
    多通道振弦数据记录仪在铁路隧道监测中的重要应用岩土工程监测是工程建设中不可或缺的一环,特别是在铁路隧道工程中更是如此。为此,振弦数据记录仪成为了一种非常重要的仪器,可以帮助监测人员实时监测隧道内部的变化,为工程的安全运行提供重要保障。本文将着重介绍多通道振弦数据记录......