使用鸿蒙系统提供的音频渲染器播放音频文件(资源)需要用到以下四个对象:
// 采样率 int sampleRate = 44100; // 音频流标志 AudioStreamFlag audioStreamFlag = AudioStreamFlag.AUDIO_STREAM_FLAG_DIRECT_OUTPUT; // 编码格式 EncodingFormat encodingFormat = EncodingFormat.ENCODING_PCM_16BIT; // 声道数 ChannelMask channelMask = ChannelMask.CHANNEL_OUT_STEREO; // 使用类型 StreamUsage streamUsage = StreamUsage.STREAM_USAGE_MEDIA; // 音频流信息 AudioStreamInfo audioStreamInfo = new AudioStreamInfo.Builder() .sampleRate(sampleRate) .audioStreamFlag(audioStreamFlag) .encodingFormat(encodingFormat) .channelMask(channelMask) .streamUsage(streamUsage) .build();
// 获取音频渲染器缓冲区大小 bufferSizeInBytes = AudioRenderer.getMinBufferSize( sampleRate, encodingFormat, channelMask);
private AudioDeviceDescriptor getSpeaker() { AudioDeviceDescriptor speaker = null; // 获取音频输出设备列表 AudioDeviceDescriptor[] devices = AudioManager.getDevices(DeviceFlag.OUTPUT_DEVICES_FLAG); // 查找扬声器设备 for (AudioDeviceDescriptor device : devices) if (device.getType() == DeviceType.SPEAKER) { speaker = device; break; } return speaker; }
// 音频渲染器信息 AudioRendererInfo audioRendererInfo = new AudioRendererInfo.Builder() .audioStreamInfo(audioStreamInfo) .bufferSizeInBytes(bufferSizeInBytes) .deviceId(getSpeaker().getId()) .isOffload(false) .build();
// 创建音频渲染器 audioRenderer = new AudioRenderer( audioRendererInfo, PlayMode.MODE_STREAM);
// 播放 audioRenderer.start(); // 异步读取音频文件 getGlobalTaskDispatcher(TaskPriority.DEFAULT) .asyncDispatch(() -> readAudioFile( openAudioFile("The Destruction of Laputa.wav")));
/storage/emulated/0/Ringtones/tmp/The Destruction of Laputa.wav
private FileDescriptor openAudioFile(String filename) { FileDescriptor fd = null; DataAbilityHelper helper = DataAbilityHelper.creator(this); ResultSet result = null; try { DataAbilityPredicates predicates = new DataAbilityPredicates( Media.DISPLAY_NAME + "='" + filename + "'"); result = helper.query(Audio.Media.EXTERNAL_DATA_ABILITY_URI, new String[]{Audio.Media.ID}, predicates); if (result.goToFirstRow()) { int id = result.getInt(0); Uri uri = Uri.appendEncodedPathToUri( Audio.Media.EXTERNAL_DATA_ABILITY_URI, String.valueOf(id)); fd = helper.openFile(uri, "r"); } } catch (Exception exception) { HiLog.info(label, exception.getLocalizedMessage()); } finally { if (result != null) result.close(); } return fd; } private void readAudioFile(FileDescriptor fd) { try { FileInputStream is = new FileInputStream(fd); byte[] buffer = new byte[bufferSizeInBytes]; int bytes; while ((bytes = is.read(buffer)) != -1) audioRenderer.write(buffer, 0, bytes); is.close(); } catch (Exception exception) { HiLog.info(label, exception.getLocalizedMessage()); } }
// 暂停 audioRenderer.pause();
// 停止 audioRenderer.stop();
// 销毁音频渲染器 audioRenderer.release();
例程:AudioRenderer
...\AudioRenderer\entry\src\main\config.json
{ ... "module": { ... "abilities": [ { ... "configChanges": [ "orientation" ] } ] } }
...\AudioRenderer\entry\src\main\resources\base\graphic\background_blue_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#00a2e8"/> </shape>
...\AudioRenderer\entry\src\main\resources\base\graphic\background_green_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#22b14c"/> </shape>
...\AudioRenderer\entry\src\main\resources\base\graphic\background_orange_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#ff7f27"/> </shape>
...\AudioRenderer\entry\src\main\resources\base\layout\ability_main.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" ohos:background_element="#804080"> <Button ohos:id="$+id:btnPlay" ohos:height="120vp" ohos:width="120vp" ohos:background_element="$graphic:background_blue_button" ohos:text="播放" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnPause" ohos:height="120vp" ohos:width="120vp" ohos:top_margin="20vp" ohos:background_element="$graphic:background_green_button" ohos:text="暂停" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnStop" ohos:height="120vp" ohos:width="120vp" ohos:top_margin="20vp" ohos:background_element="$graphic:background_orange_button" ohos:text="停止" ohos:text_size="28fp" ohos:text_color="#ffffff" /> </DirectionalLayout>
...\AudioRenderer\entry\src\main\resources\base\layout\ability_main_landscape.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="horizontal" ohos:background_element="#804080"> <Button ohos:id="$+id:btnPlay" ohos:height="120vp" ohos:width="120vp" ohos:background_element="$graphic:background_blue_button" ohos:text="播放" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnPause" ohos:height="120vp" ohos:width="120vp" ohos:left_margin="20vp" ohos:background_element="$graphic:background_green_button" ohos:text="暂停" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnStop" ohos:height="120vp" ohos:width="120vp" ohos:left_margin="20vp" ohos:background_element="$graphic:background_orange_button" ohos:text="停止" ohos:text_size="28fp" ohos:text_color="#ffffff" /> </DirectionalLayout>
...\AudioRenderer\entry\src\main\java\com\minwei\audiorenderer\slice\MainAbilitySlice.java
public class MainAbilitySlice extends AbilitySlice { private static final HiLogLabel label = new HiLogLabel( HiLog.LOG_APP, 0x00101, MainAbilitySlice.class.getCanonicalName()); private AudioRenderer audioRenderer; // 音频渲染器 private int bufferSizeInBytes; // 音频渲染器缓冲区大小 @Override public void onStart(Intent intent) { ... setButtonListener(); createAudioRenderer(); } ... @Override protected void onStop() { super.onStop(); // 停止 audioRenderer.stop(); // 销毁音频渲染器 audioRenderer.release(); } @Override protected void onOrientationChanged( DisplayOrientation displayOrientation) { super.onOrientationChanged(displayOrientation); if (displayOrientation == DisplayOrientation.LANDSCAPE) super.setUIContent( ResourceTable.Layout_ability_main_landscape); else super.setUIContent(ResourceTable.Layout_ability_main); setButtonListener(); } private void setButtonListener() { findComponentById(ResourceTable.Id_btnPlay) .setClickedListener(component -> play()); findComponentById(ResourceTable.Id_btnPause) .setClickedListener(component -> pause()); findComponentById(ResourceTable.Id_btnStop) .setClickedListener(component -> stop()); } private void createAudioRenderer() { // 采样率 int sampleRate = 44100; // 音频流标志 AudioStreamFlag audioStreamFlag = AudioStreamFlag.AUDIO_STREAM_FLAG_DIRECT_OUTPUT; // 编码格式 EncodingFormat encodingFormat = EncodingFormat.ENCODING_PCM_16BIT; // 声道数 ChannelMask channelMask = ChannelMask.CHANNEL_OUT_STEREO; // 使用类型 StreamUsage streamUsage = StreamUsage.STREAM_USAGE_MEDIA; // 音频流信息 AudioStreamInfo audioStreamInfo = new AudioStreamInfo.Builder() .sampleRate(sampleRate) .audioStreamFlag(audioStreamFlag) .encodingFormat(encodingFormat) .channelMask(channelMask) .streamUsage(streamUsage) .build(); // 获取音频渲染器缓冲区大小 bufferSizeInBytes = AudioRenderer.getMinBufferSize( sampleRate, encodingFormat, channelMask); // 音频渲染器信息 AudioRendererInfo audioRendererInfo = new AudioRendererInfo.Builder() .audioStreamInfo(audioStreamInfo) .bufferSizeInBytes(bufferSizeInBytes) .deviceId(getSpeaker().getId()) .isOffload(false) .build(); // 创建音频渲染器 audioRenderer = new AudioRenderer( audioRendererInfo, PlayMode.MODE_STREAM); } private void play() { // 播放 audioRenderer.start(); // 异步读取音频文件 getGlobalTaskDispatcher(TaskPriority.DEFAULT) .asyncDispatch(() -> readAudioFile( openAudioFile("The Destruction of Laputa.wav"))); } private void pause() { // 暂停 audioRenderer.pause(); } private void stop() { // 停止 audioRenderer.stop(); } private AudioDeviceDescriptor getSpeaker() { AudioDeviceDescriptor speaker = null; // 获取音频输出设备列表 AudioDeviceDescriptor[] devices = AudioManager.getDevices(DeviceFlag.OUTPUT_DEVICES_FLAG); // 查找扬声器设备 for (AudioDeviceDescriptor device : devices) if (device.getType() == DeviceType.SPEAKER) { speaker = device; break; } return speaker; } private FileDescriptor openAudioFile(String filename) { FileDescriptor fd = null; DataAbilityHelper helper = DataAbilityHelper.creator(this); ResultSet result = null; try { /* result = helper.query(Media.EXTERNAL_DATA_ABILITY_URI, new String[]{Audio.Media.ID, Media.DISPLAY_NAME}, null); HiLog.info(label, "找到%{public}d个音频文件", result.getRowCount()); while (result.goToNextRow()) { int id = result.getInt(0); Uri uri = Uri.appendEncodedPathToUri( Audio.Media.EXTERNAL_DATA_ABILITY_URI, String.valueOf(id)); String displayName = result.getString(1); HiLog.info(label, uri.toString() + " -> " + displayName); } */ DataAbilityPredicates predicates = new DataAbilityPredicates( Media.DISPLAY_NAME + "='" + filename + "'"); result = helper.query(Audio.Media.EXTERNAL_DATA_ABILITY_URI, new String[]{Audio.Media.ID}, predicates); if (result.goToFirstRow()) { int id = result.getInt(0); Uri uri = Uri.appendEncodedPathToUri( Audio.Media.EXTERNAL_DATA_ABILITY_URI, String.valueOf(id)); fd = helper.openFile(uri, "r"); } } catch (Exception exception) { HiLog.info(label, exception.getLocalizedMessage()); } finally { if (result != null) result.close(); } return fd; } private void readAudioFile(FileDescriptor fd) { try { FileInputStream is = new FileInputStream(fd); byte[] buffer = new byte[bufferSizeInBytes]; int bytes; while ((bytes = is.read(buffer)) != -1) audioRenderer.write(buffer, 0, bytes); is.close(); } catch (Exception exception) { HiLog.info(label, exception.getLocalizedMessage()); } } }
将音频文件"The Destruction of Laputa.wav"复制到手机外部存储的"/storage/emulated/0/Ringtones/tmp/"目录下:
运行效果如下图所示:
SoundPlayer soundPlayer = new SoundPlayer("package"); soundPlayer.playSound(SoundType.KEY_CLICK, 1.0f);
SoundPlayer soundPlayer = new SoundPlayer(); soundPlayer.createSound(ToneType.DTMF_0, 1000); soundPlayer.play();
SoundPlayer soundPlayer = new SoundPlayer( AudioVolumeType.STREAM_MUSIC.getValue()); int soundId = soundPlayer.createSound( this, ResourceTable.Media_nokia); soundPlayer.setOnCreateCompleteListener( new OnCreateCompleteListener() { @Override public void onCreateComplete( SoundPlayer soundPlayer, int soundId, int status) { if (status == 0) { int taskId = soundPlayer.play(soundId); soundPlayer.setPlaySpeedRate(taskId, 1.0f); soundPlayer.setVolume(taskId, 1.0f); soundPlayer.setLoop(taskId, 0); } } });
例程:SoundPlayer
...\SoundPlayer\entry\src\main\config.json
{ ... "module": { ... "abilities": [ { ... "configChanges": [ "orientation" ] } ] } }
...\SoundPlayer\entry\src\main\resources\base\media\nokia.mp3
...\SoundPlayer\entry\src\main\resources\base\graphic\background_blue_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#00a2e8"/> </shape>
...\SoundPlayer\entry\src\main\resources\base\graphic\background_green_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#22b14c"/> </shape>
...\SoundPlayer\entry\src\main\resources\base\graphic\background_orange_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#ff7f27"/> </shape>
...\SoundPlayer\entry\src\main\resources\base\layout\ability_main.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" ohos:background_element="#632523"> <Button ohos:id="$+id:btnSystem" ohos:height="120vp" ohos:width="120vp" ohos:background_element="$graphic:background_blue_button" ohos:text="系统音" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnTone" ohos:height="120vp" ohos:width="120vp" ohos:top_margin="20vp" ohos:background_element="$graphic:background_green_button" ohos:text="双频音" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnResource" ohos:height="120vp" ohos:width="120vp" ohos:top_margin="20vp" ohos:background_element="$graphic:background_orange_button" ohos:text="资源音" ohos:text_size="28fp" ohos:text_color="#ffffff" /> </DirectionalLayout>
...\SoundPlayer\entry\src\main\resources\base\layout\ability_main_landscape.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="horizontal" ohos:background_element="#632523"> <Button ohos:id="$+id:btnSystem" ohos:height="120vp" ohos:width="120vp" ohos:background_element="$graphic:background_blue_button" ohos:text="系统音" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnTone" ohos:height="120vp" ohos:width="120vp" ohos:left_margin="20vp" ohos:background_element="$graphic:background_green_button" ohos:text="双频音" ohos:text_size="28fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnResource" ohos:height="120vp" ohos:width="120vp" ohos:left_margin="20vp" ohos:background_element="$graphic:background_orange_button" ohos:text="资源音" ohos:text_size="28fp" ohos:text_color="#ffffff" /> </DirectionalLayout>
...\SoundPlayer\entry\src\main\java\com\minwei\soundplayer\slice\MainAbilitySlice.java
public class MainAbilitySlice extends AbilitySlice { @Override public void onStart(Intent intent) { ... setButtonListener(); } ... @Override protected void onOrientationChanged( DisplayOrientation displayOrientation) { super.onOrientationChanged(displayOrientation); if (displayOrientation == DisplayOrientation.LANDSCAPE) super.setUIContent( ResourceTable.Layout_ability_main_landscape); else super.setUIContent(ResourceTable.Layout_ability_main); setButtonListener(); } private void setButtonListener() { findComponentById(ResourceTable.Id_btnSystem) .setClickedListener(component -> playSystem()); findComponentById(ResourceTable.Id_btnTone) .setClickedListener(component -> playTone()); findComponentById(ResourceTable.Id_btnResource) .setClickedListener(component -> playResource()); } private void playSystem() { SoundPlayer soundPlayer = new SoundPlayer("packageName"); soundPlayer.playSound(SoundType.KEY_CLICK, 1.0f); } private void playTone() { SoundPlayer soundPlayer = new SoundPlayer(); soundPlayer.createSound(ToneType.DTMF_0, 1000); soundPlayer.play(); } private void playResource() { SoundPlayer soundPlayer = new SoundPlayer( AudioVolumeType.STREAM_MUSIC.getValue()); int soundId = soundPlayer.createSound( this, ResourceTable.Media_nokia); soundPlayer.setOnCreateCompleteListener( new OnCreateCompleteListener() { @Override public void onCreateComplete( SoundPlayer soundPlayer, int soundId, int status) { if (status == 0) { int taskId = soundPlayer.play(soundId); soundPlayer.setPlaySpeedRate(taskId, 1.0f); soundPlayer.setVolume(taskId, 1.0f); soundPlayer.setLoop(taskId, 0); } } }); } }
运行效果如下图所示:
public class PlayerCallback implements IPlayerCallback { @Override public void onPrepared() { // 播放开始 } @Override public void onMessage(int i, int i1) { } @Override public void onError(int i, int i1) { // 发生错误(i:错误类型,i1:错误号) } @Override public void onResolutionChanged(int i, int i1) { } @Override public void onPlayBackComplete() { // 播放结束 } @Override public void onRewindToComplete() { } @Override public void onBufferingChange(int i) { } @Override public void onNewTimedMetaData( Player.MediaTimedMetaData mediaTimedMetaData) { } @Override public void onMediaTimeIncontinuity( Player.MediaTimeInfo mediaTimeInfo) { } }
player = new Player(getContext()); // 播放音频资源 RawFileDescriptor rawFileDescriptor = getResourceManager() .getRawFileEntry("resources/rawfile/caiqin.mp3") .openRawFileDescriptor(); Source source = new Source( rawFileDescriptor.getFileDescriptor(), rawFileDescriptor.getStartPosition(), rawFileDescriptor.getFileSize()); /* 播放音频文件 FileDescriptor fileDescriptor = new FileInputStream( new File(getFilesDir() + "/caiqin.mp3")).getFD(); Source source = new Source(fileDescriptor); */ /* 播放音频链接 String URI = "https://www.audio.com/caiqin.mp3"; Source source = new Source(URI); */ player.setSource(source); player.setPlayerCallback(new PlayerCallback(this)); player.prepare(); player.play();
player.pause();
player.stop(); player.release(); player = null;
player.rewindTo(位置微秒值);
为了通过滑竿组件和时间文本实时回显播放进度,拟开启独立于UI线程的进度线程跟踪播放进度,该线程调用如下方法:
所有关乎界面的更新操作,都应该放到UI线程中执行。
getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { // 更新界面 } });
播放器中的滑竿组件既用于输出Player的当前播放位置,又用于输入用户通过拖拽滑块人为指定的播放位置。输出输入需要协调,避免相互干扰和死循环。
默认情况下横竖屏切换会导致页面被重建,如果这时正在播放,则播放过程会被打断,为了避免这种情况的发生,可在config.json文件关于相应Ability的描述中加入如下配置:
"configChanges": [ "orientation" ]
例程:AudioPlayer
...\AudioPlayer\entry\src\main\config.json
{ ... "module": { ... "abilities": [ { ... "configChanges": [ "orientation" ] } ], "reqPermissions": [ { "name": "ohos.permission.INTERNET" } ] } }
...\AudioPlayer\entry\src\main\resources\rawfile\caiqin.mp3
...\AudioPlayer\entry\src\main\resources\base\media\record.png
...\AudioPlayer\entry\src\main\resources\base\graphic\background_red_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#ff4040"/> </shape>
...\AudioPlayer\entry\src\main\resources\base\graphic\background_green_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#22b14c"/> </shape>
...\AudioPlayer\entry\src\main\resources\base\graphic\background_blue_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#00a2e8"/> </shape>
...\AudioPlayer\entry\src\main\resources\base\layout\ability_main.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:padding="10vp" ohos:background_element="#004080"> <Image ohos:height="match_parent" ohos:weight="1" ohos:width="match_parent" ohos:image_src="$media:record" /> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="center"> <Text ohos:id="$+id:txt" ohos:height="match_content" ohos:width="match_content" ohos:text="00:00:00/00:00:00" ohos:text_size="20fp" ohos:text_color="#ffffff" /> <Slider ohos:id="$+id:sld" ohos:height="match_content" ohos:width="match_parent" ohos:progress_width="5vp" ohos:progress_color="#ff7f27" /> <DirectionalLayout ohos:height="match_content" ohos:width="match_content" ohos:orientation="horizontal"> <Button ohos:id="$+id:btnPlay" ohos:height="match_content" ohos:width="match_content" ohos:padding="8vp" ohos:background_element="$graphic:background_red_button" ohos:text="播放" ohos:text_size="20fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnPause" ohos:height="match_content" ohos:width="match_content" ohos:padding="8vp" ohos:left_margin="5vp" ohos:background_element="$graphic:background_green_button" ohos:text="暂停" ohos:text_size="20fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnStop" ohos:height="match_content" ohos:width="match_content" ohos:padding="8vp" ohos:left_margin="5vp" ohos:background_element="$graphic:background_blue_button" ohos:text="停止" ohos:text_size="20fp" ohos:text_color="#ffffff" /> </DirectionalLayout> </DirectionalLayout> </DirectionalLayout>
...\AudioPlayer\entry\src\main\java\com\minwei\audioplayer\common\ProgressThread.java
public class ProgressThread extends Thread { private MainAbilitySlice slice; private int current = 0, duration; private boolean term = false; public ProgressThread(MainAbilitySlice slice) { this.slice = slice; } public void terminate() { term = true; } @Override public void run() { init(); while (!term && slice.player != null) { update(); try { sleep(1000); } catch (InterruptedException exception) {} } deinit(); } private void init() { duration = slice.player.getDuration(); updateSlider(); updateText(); } private void update() { current = this.slice.player.getCurrentTime(); if (!slice.dragging) updateCurrent(); updateText(); } private void deinit() { if (slice.player != null) { slice.player.stop(); slice.player.release(); slice.player = null; } slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { slice.btnPause.setText("暂停"); slice.slider.setProgressValue(0); slice.text.setText("00:00:00/00:00:00"); } }); } private void updateCurrent() { slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { slice.slider.setProgressValue(current); } }); } private void updateSlider() { slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { slice.slider.setMaxValue(duration); slice.slider.setProgressValue(current); } }); } private void updateText() { slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss"); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); calendar.setTimeInMillis(current); String strCurrent = formatter.format(calendar.getTime()); calendar.setTimeInMillis(duration); String strDuration = formatter.format(calendar.getTime()); slice.text.setText(strCurrent + "/" + strDuration); } }); } }
...\AudioPlayer\entry\src\main\java\com\minwei\audioplayer\common\PlayerCallback.java
public class PlayerCallback implements IPlayerCallback { private static final HiLogLabel label = new HiLogLabel( HiLog.LOG_APP, 0x00101, "PlayerCallback"); private ProgressThread thread; public PlayerCallback(MainAbilitySlice slice) { thread = new ProgressThread(slice); } @Override public void onPrepared() { HiLog.info(label, "onPrepared()"); thread.start(); } @Override public void onMessage(int i, int i1) { HiLog.info(label, "onMessage() %{public}d %{public}d", i, i1); } @Override public void onError(int i, int i1) { HiLog.info(label, "onError() %{public}d %{public}d", i, i1); } @Override public void onResolutionChanged(int i, int i1) { HiLog.info(label, "onResolutionChanged() %{public}d %{public}d", i, i1); } @Override public void onPlayBackComplete() { HiLog.info(label, "onPlayBackComplete()"); thread.terminate(); } @Override public void onRewindToComplete() { HiLog.info(label, "onRewindToComplete()"); } @Override public void onBufferingChange(int i) { HiLog.info(label, "onBufferingChange() %{public}d", i); } @Override public void onNewTimedMetaData( Player.MediaTimedMetaData mediaTimedMetaData) { HiLog.info(label, "onNewTimedMetaData()"); } @Override public void onMediaTimeIncontinuity( Player.MediaTimeInfo mediaTimeInfo) { HiLog.info(label, "onMediaTimeIncontinuity() %{public}d", mediaTimeInfo.mediaTimeUs); } }
...\AudioPlayer\entry\src\main\java\com\minwei\audioplayer\slice\MainAbilitySlice.java
public class MainAbilitySlice extends AbilitySlice { private static final HiLogLabel label = new HiLogLabel( HiLog.LOG_APP, 0x00101, "MainAbilitySlice"); public Button btnPlay, btnPause, btnStop; public Slider slider; public Text text; public Player player = null; public boolean dragging = false; @Override public void onStart(Intent intent) { ... btnPlay = (Button)findComponentById(ResourceTable.Id_btnPlay); btnPause = (Button)findComponentById(ResourceTable.Id_btnPause); btnStop = (Button)findComponentById(ResourceTable.Id_btnStop); slider = (Slider)findComponentById(ResourceTable.Id_sld); text = (Text)findComponentById(ResourceTable.Id_txt); btnPlay.setClickedListener(component -> { if (player == null) playResource(); //playFile(); //playUrl(); }); btnPause.setClickedListener(component -> { if (player != null) { if (player.isNowPlaying()) { player.pause(); getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { btnPause.setText("继续"); } }); } else { player.play(); getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { btnPause.setText("暂停"); } }); } } }); btnStop.setClickedListener(component -> { if (player != null) { player.stop(); player.release(); player = null; } }); slider.setValueChangedListener( new Slider.ValueChangedListener() { @Override public void onProgressUpdated( Slider slider, int i, boolean b) { if (player != null && dragging) player.rewindTo(i * 1000); } @Override public void onTouchStart(Slider slider) { dragging = true; } @Override public void onTouchEnd(Slider slider) { dragging = false; } }); } ... @Override protected void onStop() { if (player != null) { player.stop(); player.release(); player = null; } super.onStop(); } private void playResource() { try { player = new Player(getContext()); RawFileDescriptor rawFileDescriptor = getResourceManager() .getRawFileEntry("resources/rawfile/caiqin.mp3") .openRawFileDescriptor(); Source source = new Source( rawFileDescriptor.getFileDescriptor(), rawFileDescriptor.getStartPosition(), rawFileDescriptor.getFileSize()); player.setSource(source); player.setPlayerCallback(new PlayerCallback(this)); player.prepare(); player.play(); } catch (Exception exception) { HiLog.info(label, exception.toString()); } } private void playFile() { try { player = new Player(getContext()); FileDescriptor fileDescriptor = new FileInputStream( new File(getFilesDir() + "/caiqin.mp3")).getFD(); Source source = new Source(fileDescriptor); player.setSource(source); player.setPlayerCallback(new PlayerCallback(this)); player.prepare(); player.play(); } catch (Exception exception) { HiLog.info(label, exception.toString()); } } private void playUrl() { try { player = new Player(getContext()); String URI = "https://www.audio.com/caiqin.mp3"; Source source = new Source(URI); player.setSource(source); player.setPlayerCallback(new PlayerCallback(this)); player.prepare(); player.play(); } catch (Exception exception) { HiLog.info(label, exception.toString()); } } }
运行效果如下图所示:
<ohos.agp.components.surfaceprovider.SurfaceProvider
ohos:id="$+id:sp"
ohos:height="match_parent"
ohos:weight="1"
ohos:width="match_parent"
/>
SurfaceProvider surfaceProvider = (SurfaceProvider)findComponentById(ResourceTable.Id_sp);
public class SurfaceCallback implements SurfaceOps.Callback { @Override public void surfaceCreated(SurfaceOps surfaceOps) { // 调用Player的setVideoSurface()方法为其设置新Surface // 新Surface由surfaceOps参数的getSurface()方法获得 } @Override public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceOps surfaceOps) { } }
surfaceProvider.getSurfaceOps().get() .addCallback(new SurfaceCallback(this));
public class PlayerCallback implements IPlayerCallback { @Override public void onPrepared() { // 播放开始 } @Override public void onMessage(int i, int i1) { } @Override public void onError(int i, int i1) { // 发生错误(i:错误类型,i1:错误号) } @Override public void onResolutionChanged(int i, int i1) { } @Override public void onPlayBackComplete() { // 播放结束 } @Override public void onRewindToComplete() { } @Override public void onBufferingChange(int i) { } @Override public void onNewTimedMetaData( Player.MediaTimedMetaData mediaTimedMetaData) { } @Override public void onMediaTimeIncontinuity( Player.MediaTimeInfo mediaTimeInfo) { } }
player = new Player(getContext()); // 播放视频资源 RawFileDescriptor rawFileDescriptor = getResourceManager() .getRawFileEntry("resources/rawfile/wow.mp4") .openRawFileDescriptor(); Source source = new Source( rawFileDescriptor.getFileDescriptor(), rawFileDescriptor.getStartPosition(), rawFileDescriptor.getFileSize()); /* 播放视频文件 FileDescriptor fileDescriptor = new FileInputStream( new File(getFilesDir() + "/wow.mp4")).getFD(); Source source = new Source(fileDescriptor); */ /* 播放视频链接 String URI = "https://www.video.com/wow.mp4"; Source source = new Source(URI); */ player.setSource(source); player.setVideoSurface( surfaceProvider.getSurfaceOps().get().getSurface()); player.setPlayerCallback(new PlayerCallback(this)); player.enableScreenOn(true); player.prepare(); player.play();
player.pause();
player.stop(); player.release(); player = null;
player.rewindTo(位置微秒值);
为了通过滑竿组件和时间文本实时回显播放进度,拟开启独立于UI线程的进度线程跟踪播放进度,该线程调用如下方法:
所有关乎界面的更新操作,都应该放到UI线程中执行。
getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { // 更新界面 } });
播放器中的滑竿组件既用于输出Player的当前播放位置,又用于输入用户通过拖拽滑块人为指定的播放位置。输出输入需要协调,避免相互干扰和死循环。
默认情况下横竖屏切换会导致页面被重建,如果这时正在播放,则播放过程会被打断,为了避免这种情况的发生,可在config.json文件关于相应Ability的描述中加入如下配置:
"configChanges": [ "orientation" ]
例程:VideoPlayer
...\VideoPlayer\entry\src\main\config.json
{ ... "module": { ... "abilities": [ { ... "configChanges": [ "orientation" ] } ], "reqPermissions": [ { "name": "ohos.permission.INTERNET" } ] } }
...\VideoPlayer\entry\src\main\resources\rawfile\wow.mp4
...\VideoPlayer\entry\src\main\resources\base\graphic\background_red_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#ff4040"/> </shape>
...\VideoPlayer\entry\src\main\resources\base\graphic\background_green_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#22b14c"/> </shape>
...\VideoPlayer\entry\src\main\resources\base\graphic\background_blue_button.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:shape="oval"> <solid ohos:color="#00a2e8"/> </shape>
...\VideoPlayer\entry\src\main\resources\base\layout\ability_main.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:padding="10vp" ohos:background_element="#000000"> <DirectionalLayout ohos:height="match_parent" ohos:weight="1" ohos:width="match_parent" ohos:alignment="center"> <ohos.agp.components.surfaceprovider.SurfaceProvider ohos:id="$+id:sp" ohos:height="180vp" ohos:width="320vp" /> </DirectionalLayout> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="center"> <Text ohos:id="$+id:txt" ohos:height="match_content" ohos:width="match_content" ohos:text="00:00:00/00:00:00" ohos:text_size="20fp" ohos:text_color="#ffffff" /> <Slider ohos:id="$+id:sld" ohos:height="match_content" ohos:width="match_parent" ohos:progress_width="5vp" ohos:progress_color="#ff7f27" /> <DirectionalLayout ohos:height="match_content" ohos:width="match_content" ohos:orientation="horizontal"> <Button ohos:id="$+id:btnPlay" ohos:height="match_content" ohos:width="match_content" ohos:padding="8vp" ohos:background_element="$graphic:background_red_button" ohos:text="播放" ohos:text_size="20fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnPause" ohos:height="match_content" ohos:width="match_content" ohos:padding="8vp" ohos:left_margin="5vp" ohos:background_element="$graphic:background_green_button" ohos:text="暂停" ohos:text_size="20fp" ohos:text_color="#ffffff" /> <Button ohos:id="$+id:btnStop" ohos:height="match_content" ohos:width="match_content" ohos:padding="8vp" ohos:left_margin="5vp" ohos:background_element="$graphic:background_blue_button" ohos:text="停止" ohos:text_size="20fp" ohos:text_color="#ffffff" /> </DirectionalLayout> </DirectionalLayout> </DirectionalLayout>
...\VideoPlayer\entry\src\main\java\com\minwei\videoplayer\common\SurfaceCallback.java
public class SurfaceCallback implements Callback { private static final HiLogLabel label = new HiLogLabel( HiLog.LOG_APP, 0x00101, "SurfaceCallback"); private MainAbilitySlice slice; public SurfaceCallback(MainAbilitySlice slice) { this.slice = slice; } @Override public void surfaceCreated(SurfaceOps surfaceOps) { HiLog.info(label, "surfaceCreated()"); if (slice.player != null) slice.player.setVideoSurface(surfaceOps.getSurface()); } @Override public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) { HiLog.info(label, "surfaceChanged() %{public}d %{public}d %{public}d", i, i1, i2); } @Override public void surfaceDestroyed(SurfaceOps surfaceOps) { HiLog.info(label, "surfaceDestroyed()"); } }
...\VideoPlayer\entry\src\main\java\com\minwei\videoplayer\common\ProgressThread.java
public class ProgressThread extends Thread { private MainAbilitySlice slice; private int current = 0, duration; private boolean term = false; public ProgressThread(MainAbilitySlice slice) { this.slice = slice; } public void terminate() { term = true; } @Override public void run() { init(); while (!term && slice.player != null) { update(); try { sleep(1000); } catch (InterruptedException exception) {} } deinit(); } private void init() { duration = slice.player.getDuration(); updateSlider(); updateText(); } private void update() { current = this.slice.player.getCurrentTime(); if (!slice.dragging) updateCurrent(); updateText(); } private void deinit() { if (slice.player != null) { slice.player.stop(); slice.player.release(); slice.player = null; } slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { slice.surfaceProvider.removeFromWindow(); slice.btnPause.setText("暂停"); slice.slider.setProgressValue(0); slice.text.setText("00:00:00/00:00:00"); } }); } private void updateCurrent() { slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { slice.slider.setProgressValue(current); } }); } private void updateSlider() { slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { slice.slider.setMaxValue(duration); slice.slider.setProgressValue(current); } }); } private void updateText() { slice.getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss"); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); calendar.setTimeInMillis(current); String strCurrent = formatter.format(calendar.getTime()); calendar.setTimeInMillis(duration); String strDuration = formatter.format(calendar.getTime()); slice.text.setText(strCurrent + "/" + strDuration); } }); } }
...\VideoPlayer\entry\src\main\java\com\minwei\videoplayer\common\PlayerCallback.java
public class PlayerCallback implements IPlayerCallback { private static final HiLogLabel label = new HiLogLabel( HiLog.LOG_APP, 0x00101, "PlayerCallback"); private ProgressThread thread; public PlayerCallback(MainAbilitySlice slice) { thread = new ProgressThread(slice); } @Override public void onPrepared() { HiLog.info(label, "onPrepared()"); thread.start(); } @Override public void onMessage(int i, int i1) { HiLog.info(label, "onMessage() %{public}d %{public}d", i, i1); } @Override public void onError(int i, int i1) { HiLog.info(label, "onError() %{public}d %{public}d", i, i1); } @Override public void onResolutionChanged(int i, int i1) { HiLog.info(label, "onResolutionChanged() %{public}d %{public}d", i, i1); } @Override public void onPlayBackComplete() { HiLog.info(label, "onPlayBackComplete()"); thread.terminate(); } @Override public void onRewindToComplete() { HiLog.info(label, "onRewindToComplete()"); } @Override public void onBufferingChange(int i) { HiLog.info(label, "onBufferingChange() %{public}d", i); } @Override public void onNewTimedMetaData( Player.MediaTimedMetaData mediaTimedMetaData) { HiLog.info(label, "onNewTimedMetaData()"); } @Override public void onMediaTimeIncontinuity( Player.MediaTimeInfo mediaTimeInfo) { HiLog.info(label, "onMediaTimeIncontinuity() %{public}d", mediaTimeInfo.mediaTimeUs); } }
...\VideoPlayer\entry\src\main\java\com\minwei\videoplayer\slice\MainAbilitySlice.java
public class MainAbilitySlice extends AbilitySlice { private static final HiLogLabel label = new HiLogLabel( HiLog.LOG_APP, 0x00101, "MainAbilitySlice"); public SurfaceProvider surfaceProvider; public Button btnPlay, btnPause, btnStop; public Slider slider; public Text text; public Player player = null; public boolean dragging = false; @Override public void onStart(Intent intent) { ... surfaceProvider = (SurfaceProvider)findComponentById( ResourceTable.Id_sp); btnPlay = (Button)findComponentById(ResourceTable.Id_btnPlay); btnPause = (Button)findComponentById(ResourceTable.Id_btnPause); btnStop = (Button)findComponentById(ResourceTable.Id_btnStop); slider = (Slider)findComponentById(ResourceTable.Id_sld); text = (Text)findComponentById(ResourceTable.Id_txt); surfaceProvider.getSurfaceOps().get() .addCallback(new SurfaceCallback(this)); //surfaceProvider.pinToZTop(true); surfaceProvider.pinToZTop(false); WindowManager.getInstance() .getTopWindow().get().setTransparent(true); btnPlay.setClickedListener(component -> { if (surfaceProvider.getSurfaceOps().isPresent() && player == null) playResource(); //playFile(); //playUrl(); }); btnPause.setClickedListener(component -> { if (player != null) { if (player.isNowPlaying()) { player.pause(); getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { btnPause.setText("继续"); } }); } else { player.play(); getUITaskDispatcher().asyncDispatch(new Runnable() { @Override public void run() { btnPause.setText("暂停"); } }); } } }); btnStop.setClickedListener(component -> { if (player != null) { player.stop(); player.release(); player = null; } }); slider.setValueChangedListener( new Slider.ValueChangedListener() { @Override public void onProgressUpdated( Slider slider, int i, boolean b) { if (player != null && dragging) player.rewindTo(i * 1000); } @Override public void onTouchStart(Slider slider) { dragging = true; } @Override public void onTouchEnd(Slider slider) { dragging = false; } }); } ... @Override protected void onStop() { if (player != null) { player.stop(); player.release(); player = null; } super.onStop(); } private void playResource() { try { player = new Player(getContext()); RawFileDescriptor rawFileDescriptor = getResourceManager() .getRawFileEntry("resources/rawfile/wow.mp4") .openRawFileDescriptor(); Source source = new Source( rawFileDescriptor.getFileDescriptor(), rawFileDescriptor.getStartPosition(), rawFileDescriptor.getFileSize()); player.setSource(source); player.setVideoSurface( surfaceProvider.getSurfaceOps().get().getSurface()); player.setPlayerCallback(new PlayerCallback(this)); player.enableScreenOn(true); player.prepare(); player.play(); } catch (Exception exception) { HiLog.info(label, exception.toString()); } } private void playFile() { try { player = new Player(getContext()); FileDescriptor fileDescriptor = new FileInputStream( new File(getFilesDir() + "/wow.mp4")).getFD(); Source source = new Source(fileDescriptor); player.setSource(source); player.setVideoSurface( surfaceProvider.getSurfaceOps().get().getSurface()); player.setPlayerCallback(new PlayerCallback(this)); player.enableScreenOn(true); player.prepare(); player.play(); } catch (Exception exception) { HiLog.info(label, exception.toString()); } } private void playUrl() { try { player = new Player(getContext()); String URI = "https://www.video.com/wow.mp4"; Source source = new Source(URI); player.setSource(source); player.setVideoSurface( surfaceProvider.getSurfaceOps().get().getSurface()); player.setPlayerCallback(new PlayerCallback(this)); player.enableScreenOn(true); player.prepare(); player.play(); } catch (Exception exception) { HiLog.info(label, exception.toString()); } } }
运行效果如下图所示: