• <nav id="26o4m"><source id="26o4m"></source></nav>
  • 鸿蒙应用开发:实现简单的媒体播放器

    ZEGO即构科技 博客主编? 2021-12-10 15:06:56

    距离 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 onError(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 版本,并正式启动公测!

    ?

    ...全文
    245 回复 点赞 打赏 收藏 举报
    写回复
    回复
    切换为时间正序
    请发表友善的回复…
    发表回复
    相关推荐
    发帖
    RTC即刻构建
    创建于2021-06-07

    224

    社区成员

    音视频技术干货的分享聚集!
    帖子事件
    创建了帖子
    2021-12-10 15:06
    社区公告

    这里是音视频开发者小伙伴的聚集大本营!大家可在此集中讨论RTC开发相关内容、工作中遇到的问题、资源求助等。即构期待与大家共同打造一个开放、包容的RTC构建交流社区~

    天天躁夜夜躁狠狠综合2020,日本二区三区欧美亚洲国,自拍另类欧美图区视频一区,青青草国产免费无码