鸿蒙系统HarmonyOS,今天来写一个视频播放器。
官方给的例子比较简单。
官方示例参考
本例可以实现视频播放、暂停、重播、画面显示、拖拽视频进度。
看最后界面。
图1本文不细讲,它是增加了弹框授权。
图2是开始状态,图三是播放状态。
图2现在没有视频预览,有空再做。
准备。
需要准备resources/rawfile/video_1.mp4,
resources/base/graphic/icon_play.xml,
resources/base/graphic/icon_pause.xml,
resources/base/graphic/icon_redo.xml,
4个文件。
其中3个xml文件是用svg文件转化成xml文件的。
先在iconfont-阿里巴巴矢量图标库网上下载几个播放、暂停图标文件,选择下载svg文件,
然后在DevEco Studio->module右键->New->Svg To xml,就能把svg文件转化成xml文件了。
下面给个resources/base/graphic/icon_play.xml的示例。
<?xml version="1.0" encoding="UTF-8"?>
<vector xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:width="200vp" ohos:height="200vp" ohos:viewportWidth="1024" ohos:viewportHeight="1024">
<path ohos:fillColor="#FF000000" ohos:pathData="M157.54,860.55V163.45c0,-19.69 25.6,-33.48 43.32,-17.72l653.78,340.68c15.75,11.82 15.75,37.42 0,49.23L200.86,880.25c-17.72,13.78 -43.32,1.97 -43.32,-19.69z"></path>
</vector>
在DevEco Studio中新建一个Ability吧,取名SampleVideoPlayerAbility,
会得到
src/main/java/com/example/myapplication/slice/SampleVideoPlayerAbilitySlice.java,
src/main/java/com/example/myapplication/video/SampleVideoPlayerAbility.java,
resources/base/layout/ability_sample_video_player.xml,
3个文件。
本例中,实现代码在SampleVideoPlayerAbilitySlice.java文件中
布局在文件ability_sample_video_player.xml中。
上布局文件ability_sample_video_player.xml。
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="vertical">
<SurfaceProvider
ohos:id="$+id:sp"
ohos:height="200vp"
ohos:width="match_parent"
>
</SurfaceProvider>
<DependentLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:alignment="center"
>
<Slider
ohos:id="$+id:slider"
ohos:height="40vp"
ohos:width="match_parent"
ohos:max="6"
ohos:min="0"
ohos:progress="2"
ohos:progress_element="#00FF00"
ohos:progress_hint_text_alignment="end"
ohos:progress_hint_text_color="#ff0000"
ohos:progress_hint_text_size="20fp"
>
</Slider>
<Text
ohos:id="$+id:text_slider_max"
ohos:height="match_content"
ohos:width="match_content"
ohos:align_parent_end="true"
ohos:background_element="$graphic:background_ability_main_video"
ohos:below="$id:slider"
ohos:layout_alignment="horizontal_center"
ohos:text="0"
ohos:text_size="20vp"
/>
<Text
ohos:id="$+id:text_slider_current"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_ability_main_video"
ohos:below="$id:slider"
ohos:layout_alignment="horizontal_center"
ohos:text="0s"
ohos:text_size="20vp"
/>
</DependentLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_content"
ohos:alignment="center"
ohos:orientation="horizontal">
<Button
ohos:id="$+id:button_play_or_pause"
ohos:height="50vp"
ohos:width="50vp"
ohos:background_element="$graphic:icon_play"
></Button>
<Button
ohos:id="$+id:button_restart"
ohos:height="50vp"
ohos:width="50vp"
ohos:background_element="$graphic:icon_redo"
></Button>
</DirectionalLayout>
</DirectionalLayout>
上Slice.java文件。重点部分在注释里说明了。
package com.example.myapplication.slice;
import com.example.myapplication.ResourceTable;
import com.example.myapplication.utils.Common;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Slider;
import ohos.agp.components.Text;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.VectorElement;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.window.dialog.ToastDialog;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.global.resource.RawFileEntry;
import ohos.media.common.Source;
import ohos.media.player.Player;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
public class SampleVideoPlayerAbilitySlice extends AbilitySlice {
private Slider slider = null;
private Button buttonPlay = null, buttonRestart = null;
private Text textSliderMax = null;
private Text textSliderCurrent = null;
private SurfaceProvider sp = null;
private MyEventHandler handler = null;
private Player player = null;
// 可选,在本例中实现视频进度拖拽
private Slider.ValueChangedListener sliderValueChangedListener = new Slider.ValueChangedListener() {
int position = 0;
@Override
public void onProgressUpdated(Slider slider, int i, boolean b) {
Common.d("onProgressUpdated() " + i);
position = i;
//不能在此拖拽视频
}
@Override
public void onTouchStart(Slider slider) {
Common.d("onTouchStart() ");
}
@Override
public void onTouchEnd(Slider slider) {
if (player != null) {
Common.d("onTouchEnd()getCurrentTime()=" + player.getCurrentTime());
} else {
Common.d("onTouchEnd() ");
}
//拖拽视频
player.rewindTo(position * 1000);
}
};
// 播放完成 、播放出错、拖拽完成回调函数
private Player.IPlayerCallback playerCallback = new Player.IPlayerCallback() {
@Override
public void onPrepared() {
Common.d("onPrepared()");
}
@Override
public void onMessage(int i, int i1) {
Common.d("onMessage()");
}
@Override
public void onError(int i, int i1) {
Common.d("onError()");
}
@Override
public void onResolutionChanged(int i, int i1) {
Common.d("onResolutionChanged()");
}
@Override
public void onPlayBackComplete() {
//播放完毕
Common.d("onPlayBackComplete()");
Common.d("onPlayBackComplete()getCurrentTime()=" + player.getCurrentTime());
player.pause();
InnerEvent event = InnerEvent.get(handler.MSG_UPDATE_BUTTON_PAUSED);
handler.sendEvent(event);
}
@Override
public void onRewindToComplete() {
// 视频player.rewindTo()执行完成
Common.d("onRewindToComplete()");
Common.d("onRewindToComplete()getCurrentTime()=" + player.getCurrentTime());
}
@Override
public void onBufferingChange(int i) {
Common.d("onBufferingChange()");
}
@Override
public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {
Common.d("onNewTimedMetaData()");
}
@Override
public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
if (player != null) {
Common.d("onMediaTimeIncontinuity()getCurrentTime()=" + player.getCurrentTime());
if (false) {
//onMediaTimeIncontinuity()并不会每秒更新,所以此函数不适合更新进度条。
InnerEvent event = InnerEvent.get(handler.MSG_UPDATE_PROGRESS);
handler.sendEvent(event);
}
} else {
Common.d("onMediaTimeIncontinuity()");
}
}
};
// 可选,在本例中没有使用
private SurfaceOps.Callback surfaceOpsCallBack = new SurfaceOps.Callback() {
@Override
public void surfaceCreated(SurfaceOps surfaceOps) {
//Common.d("surfaceCreated()");
}
@Override
public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {
//Common.d("surfaceChanged()");
}
@Override
public void surfaceDestroyed(SurfaceOps surfaceOps) {
//Common.d("surfaceDestroyed()");
}
};
private Component.ClickedListener listener = new Component.ClickedListener() {
@Override
public void onClick(Component component) {
switch (component.getId()) {
case ResourceTable.Id_button_play_or_pause:
//点一下播放,再点一下暂停
handlePlayOrPause();
break;
case ResourceTable.Id_button_restart:
//从头播放、重新播放
if (player == null) {
} else if (player.isNowPlaying()) {
rewind();
} else {
handlePlayOrPause();
}
break;
}
}
};
@Override
public void onStart(Intent intent) {
Common.d("video", "onStart");
super.onStart(intent);
//设置布局
Common.d("before setUIContent");
super.setUIContent(ResourceTable.Layout_ability_sample_video_player);
Common.d("after setUIContent");
buttonPlay = (Button) findComponentById(ResourceTable.Id_button_play_or_pause);
buttonRestart = (Button) findComponentById(ResourceTable.Id_button_restart);
buttonPlay.setClickedListener(listener);
buttonRestart.setClickedListener(listener);
//进度条可以拖拽
slider = (Slider) findComponentById(ResourceTable.Id_slider);
slider.setValueChangedListener(sliderValueChangedListener);
// handler的处理函数运行在主线程中,用来更新界面
// 如果参数传入用EventRunner.create(),则handler的处理函数在子线程中运行,并不在主线程中运行。
handler = new MyEventHandler(EventRunner.current());
textSliderMax = (Text) findComponentById(ResourceTable.Id_text_slider_max);
textSliderCurrent = (Text) findComponentById(ResourceTable.Id_text_slider_current);
//视频内容显示在SurfaceProvider组件中
sp = (SurfaceProvider) findComponentById(ResourceTable.Id_sp);
sp.pinToZTop(true);
sp.getSurfaceOps().get().addCallback(surfaceOpsCallBack);
// 可忽略,弹框授权
Common.grand(this, "ohos.permission.READ_MEDIA");
Common.d("onStart() ---");
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
private void rewind() {
player.rewindTo(0);
}
private void handlePlayOrPause() {
Common.d("handlePlayOrPause()");
try {
if (player == null) {
//首次播放
Common.d("handlePlayOrPause() try to play from start");
play();
Element element = new VectorElement(this, ResourceTable.Graphic_icon_pause);
buttonPlay.setBackground(element);
new ToastDialog(getContext())
.setText("playing")
// Toast显示在界面底部
.setAlignment(LayoutAlignment.BOTTOM)
.show();
} else if (!player.isNowPlaying()) {
//暂停了或者播放完成了,则继续播放或者从头播放
Common.d("handlePlayOrPause() go on playing");
player.play();
Element element = new VectorElement(this, ResourceTable.Graphic_icon_pause);
buttonPlay.setBackground(element);
new ToastDialog(getContext())
.setText("playing")
.setAlignment(LayoutAlignment.BOTTOM)
.show();
} else if (player.isNowPlaying()) {
Common.d("handlePlayOrPause() try to pause");
pause();
Element element = new VectorElement(this, ResourceTable.Graphic_icon_play);
buttonPlay.setBackground(element);
new ToastDialog(getContext())
.setText("paused")
.setAlignment(LayoutAlignment.BOTTOM)
.show();
}
} catch (Exception e) {
e.printStackTrace();
Common.d("e:" + e);
}
}
public void preparePlayer() {
// 步骤 1:实例化对象
if (player == null) {
player = new Player(this);
}
try {
if (true) {
//此文件放在rawfile/目录中
RawFileEntry entry = getResourceManager().getRawFileEntry("resources/rawfile/vid_2.mp4");//这一行和官方例子有点不一样 请注意
FileDescriptor fd = null;
fd = entry.openRawFileDescriptor().getFileDescriptor();
Common.d("fd=" + fd + " valid=" + fd.valid());
player.setSource(entry.openRawFileDescriptor());
} else {
//此文件放在sdcard中,但我运行总说没有权限读取文件。
FileInputStream fileInputStream = new FileInputStream(new File("/sdcard/xxx.mp4"));
FileDescriptor fd = fileInputStream.getFD();
Source source = new Source(fd);
player.setSource(source);
}
player.prepare();
//设置显示在sp中
player.setVideoSurface(sp.getSurfaceOps().get().getSurface());
//播放完成或者播放出错的回调函数
player.setPlayerCallback(playerCallback);
//ms为单位,视频时长
int duration = player.getDuration();
Common.d("duration=" + duration);
slider.setMaxValue(duration);
textSliderMax.setText((duration + 500) / 1000 + "s");
textSliderCurrent.setText("0s");
} catch (IOException e) {
e.printStackTrace();
}
}
public void play() {
Common.d("play()");
preparePlayer();
try {
//因为Player的PlayerCallback中onMediaTimeIncontinuity()并不会每秒更新,
//所以在onMediaTimeIncontinuity()中更新进度条的话,进度条可能长时间不动,一动就是好几秒过去了
//Player的PlayerCallback中没有一个函数是每秒更新的,
//所以PlayerCallback()中不适合更新进度条。
//还是用线程每秒更新吧
new Thread(new Runnable() {
@Override
public void run() {
Common.d("run() duration=" + player.getDuration());
int duration = player.getDuration();
for (int i = 0; i < (duration + 500) / 1000; i++) {
if (player == null) break;
if (player.isNowPlaying()) {
InnerEvent event = InnerEvent.get(handler.MSG_UPDATE_PROGRESS);
handler.sendEvent(event);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//set progress --
player.play();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
Common.d("stop()");
if (player != null) {
player.stop();
player.release();
player = null;
}
}
public void pause() {
if (player != null) {
player.pause();
}
}
public class MyEventHandler extends EventHandler {
public final int MSG_UPDATE_PROGRESS = 1;
public final int MSG_UPDATE_BUTTON_PLAYING = 2;
public final int MSG_UPDATE_BUTTON_PAUSED = 3;
public MyEventHandler(EventRunner runner) throws IllegalArgumentException {
super(runner);
}
@Override
protected void processEvent(InnerEvent event) {
//运行在主线程中
super.processEvent(event);
int eventId = event.eventId;
switch (eventId) {
case MSG_UPDATE_PROGRESS:
int duration = player.getDuration();
int currentTime = player.getCurrentTime();
int delta = (duration - currentTime + 500) / 1000;
String t = delta + " s remaining";
Common.d("hint1:" + t + " progress=" + (currentTime + 500) / 1000);
slider.setProgressValue(currentTime);
slider.setProgressHintText(t);
textSliderCurrent.setText((currentTime + 500) / 1000 + "s");
Common.d("hint2:" + slider.getProgressHintText() + " progress=" + slider.getProgress());
break;
case MSG_UPDATE_BUTTON_PLAYING:
Element element = new VectorElement(SampleVideoPlayerAbilitySlice.this, ResourceTable.Graphic_icon_pause);
buttonPlay.setBackground(element);
break;
case MSG_UPDATE_BUTTON_PAUSED:
Element elementPaused = new VectorElement(SampleVideoPlayerAbilitySlice.this, ResourceTable.Graphic_icon_play);
buttonPlay.setBackground(elementPaused);
break;
}
}
}
}
有任何问题欢迎指正!
如果觉得有用的话,就请我喝瓶水吧~~~