From a804239bf008fccf20b2496a1e598f6e2260cb93 Mon Sep 17 00:00:00 2001 From: gaohui Date: Fri, 2 Jul 2021 10:16:19 +0800 Subject: [PATCH] Add new sample AudioPlayer Signed-off-by: gaohui --- media/AudioPlayer/README.md | 11 + media/AudioPlayer/README_zh.md | 12 + media/AudioPlayer/build.gradle | 48 +++ media/AudioPlayer/entry/build.gradle | 33 ++ media/AudioPlayer/entry/src/main/config.json | 81 ++++ .../samples/audioplayer/AVPlayClient.java | 360 ++++++++++++++++++ .../samples/audioplayer/AVPlayService.java | 270 +++++++++++++ .../samples/audioplayer/OnlinePlayClient.java | 126 ++++++ .../audioplayer/model/AVElementManager.java | 161 ++++++++ .../samples/audioplayer/utils/LogUtil.java | 75 ++++ .../audioplayer/utils/ThreadPoolManager.java | 82 ++++ .../main/resources/base/element/string.json | 32 ++ .../base/graphic/list_item_background.xml | 25 ++ .../main/resources/base/layout/list_item.xml | 22 ++ .../base/layout/main_play_layout.xml | 131 +++++++ .../base/layout/online_play_layout.xml | 44 +++ .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes media/AudioPlayer/screenshots/phone/main.png | Bin 0 -> 76464 bytes .../AudioPlayer/screenshots/phone/online.png | Bin 0 -> 91989 bytes media/AudioPlayer/settings.gradle | 15 + 20 files changed, 1528 insertions(+) create mode 100644 media/AudioPlayer/README.md create mode 100644 media/AudioPlayer/README_zh.md create mode 100644 media/AudioPlayer/build.gradle create mode 100644 media/AudioPlayer/entry/build.gradle create mode 100644 media/AudioPlayer/entry/src/main/config.json create mode 100644 media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayClient.java create mode 100644 media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayService.java create mode 100644 media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/OnlinePlayClient.java create mode 100644 media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/model/AVElementManager.java create mode 100644 media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/LogUtil.java create mode 100644 media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/ThreadPoolManager.java create mode 100644 media/AudioPlayer/entry/src/main/resources/base/element/string.json create mode 100644 media/AudioPlayer/entry/src/main/resources/base/graphic/list_item_background.xml create mode 100644 media/AudioPlayer/entry/src/main/resources/base/layout/list_item.xml create mode 100644 media/AudioPlayer/entry/src/main/resources/base/layout/main_play_layout.xml create mode 100644 media/AudioPlayer/entry/src/main/resources/base/layout/online_play_layout.xml create mode 100644 media/AudioPlayer/entry/src/main/resources/base/media/icon.png create mode 100644 media/AudioPlayer/screenshots/phone/main.png create mode 100644 media/AudioPlayer/screenshots/phone/online.png create mode 100644 media/AudioPlayer/settings.gradle diff --git a/media/AudioPlayer/README.md b/media/AudioPlayer/README.md new file mode 100644 index 0000000000..5ca81ac0e6 --- /dev/null +++ b/media/AudioPlayer/README.md @@ -0,0 +1,11 @@ +# Media Session + +- The media playback control module consists of the browser \(**AVBrowser**\), controller \(**AVController**\), browser service \(**AVBrowserService**\), and media session \(**AVSession**\). The four classes are the core for your applications to control media playback. + + This sample shows how to use the provided APIs to develop music playback functions, including playing, pausing, jumping to the previous or next item, showing the progress, and switching among items in the playback list. The playback list allows your users to select the item to play. + + +- Licensing + +For details, see the LICENSE. + diff --git a/media/AudioPlayer/README_zh.md b/media/AudioPlayer/README_zh.md new file mode 100644 index 0000000000..5cfa204e42 --- /dev/null +++ b/media/AudioPlayer/README_zh.md @@ -0,0 +1,12 @@ +# 媒体会话 + +- 音视频播放控制框架,主要包括浏览器(AVBrowser)、控制器(AVController)、浏览器服务(AVBrowserService)、会话(AVSession)四部分组成。这四部分构成了音视频播放控制框架的核心。 + + 本示例展示了如何使用音视频播放控制框架实现音乐播放功能。主要功能包括了曲目播放、暂停、上一首、下一首、播放进度、跳转曲目列表等功能。曲目列表展示曲目名称,并选中播放曲目。 + + +- Licensing + + 请查阅 LICENSE。 + + diff --git a/media/AudioPlayer/build.gradle b/media/AudioPlayer/build.gradle new file mode 100644 index 0000000000..dede97233a --- /dev/null +++ b/media/AudioPlayer/build.gradle @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +apply plugin: 'com.huawei.ohos.app' + +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 4 + } +} +buildscript { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + jcenter() + } + dependencies { + classpath 'com.huawei.ohos:hap:2.4.4.2' + } +} +allprojects { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + jcenter() + } +} \ No newline at end of file diff --git a/media/AudioPlayer/entry/build.gradle b/media/AudioPlayer/entry/build.gradle new file mode 100644 index 0000000000..3b840fe578 --- /dev/null +++ b/media/AudioPlayer/entry/build.gradle @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +apply plugin: 'com.huawei.ohos.hap' +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 4 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/media/AudioPlayer/entry/src/main/config.json b/media/AudioPlayer/entry/src/main/config.json new file mode 100644 index 0000000000..486b625dae --- /dev/null +++ b/media/AudioPlayer/entry/src/main/config.json @@ -0,0 +1,81 @@ +{ + "app": { + "bundleName": "ohos.samples.audioplayer", + "version": { + "code": 1000000, + "name": "1.0" + } + }, + "deviceConfig": {}, + "module": { + "package": "ohos.samples.audioplayer", + "name": ".AudioPlayer", + "reqCapabilities": [ + "video_support" + ], + "metaData": {}, + "deviceType": [ + "default", + "tv" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry", + "installationFree":false + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "formsEnabled": false, + "name": ".AVPlayClient", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + }, + { + "skills": [ + { + "actions": [ + "action.media.browse.AVBrowserService" + ] + } + ], + "backgroundModes": [ + "audioPlayback" + ], + "name": ".AVPlayService", + "type": "service", + "launchType": "standard" + }, + { + "name": ".OnlinePlayClient", + "type": "page", + "launchType": "standard" + } + ], + "reqPermissions": [ + { + "name": "ohos.permission.READ_USER_STORAGE" + }, + { + "name": "ohos.permission.INTERNET" + }, + { + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" + } + ] + } +} \ No newline at end of file diff --git a/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayClient.java b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayClient.java new file mode 100644 index 0000000000..efb9010712 --- /dev/null +++ b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayClient.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.samples.audioplayer; + +import ohos.samples.audioplayer.utils.LogUtil; + +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; +import ohos.aafwk.content.Operation; +import ohos.agp.components.BaseItemProvider; +import ohos.agp.components.Button; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.LayoutScatter; +import ohos.agp.components.ListContainer; +import ohos.agp.components.RecycleItemProvider; +import ohos.agp.components.Text; +import ohos.bundle.ElementName; +import ohos.bundle.IBundleManager; +import ohos.media.common.AVMetadata; +import ohos.media.common.sessioncore.AVConnectionCallback; +import ohos.media.common.sessioncore.AVControllerCallback; +import ohos.media.common.sessioncore.AVElement; +import ohos.media.common.sessioncore.AVPlaybackState; +import ohos.media.common.sessioncore.AVQueueElement; +import ohos.media.common.sessioncore.AVSubscriptionCallback; +import ohos.media.sessioncore.AVBrowser; +import ohos.media.sessioncore.AVController; +import ohos.security.SystemPermission; +import ohos.utils.PacMap; +import ohos.utils.net.Uri; + +import java.util.ArrayList; +import java.util.List; + +/** + * The client of the player, that implements a AVBrowser. + */ +public class AVPlayClient extends Ability { + private static final String TAG = AVPlayClient.class.getSimpleName(); + + // provide the main client function of media framework + private AVBrowser avBrowser; + + // provide the operations by the avBrowser + private AVController avController; + + private Button playButton; + + private Button previousButton; + + private Button nextButton; + + private Text titleText; + + private Text progressText; + + private Text totalTimeText; + + // the media list got from the service + private List avElementList = new ArrayList<>(); + + private AVElementItemProvider avElementsListItemProvider; + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + LogUtil.info(TAG, "onStart"); + super.setUIContent(ResourceTable.Layout_main_play_layout); + + // bind the handler for all the components + previousButton = (Button) findComponentById(ResourceTable.Id_to_previous_button); + previousButton.setClickedListener(component -> handlerPrevEvent()); + nextButton = (Button) findComponentById(ResourceTable.Id_to_next_button); + nextButton.setClickedListener(component -> handlerNextEvent()); + playButton = (Button) findComponentById(ResourceTable.Id_play_button); + playButton.setClickedListener(component -> handlerPlayEvent()); + + titleText = (Text) findComponentById(ResourceTable.Id_audio_title); + progressText = (Text) findComponentById(ResourceTable.Id_audio_progress); + totalTimeText = (Text) findComponentById(ResourceTable.Id_audio_total_time); + ListContainer avElementsListContainer = (ListContainer) findComponentById(ResourceTable.Id_play_list); + + avElementsListItemProvider = new AVElementItemProvider(); + avElementsListContainer.setItemProvider(avElementsListItemProvider); + Component jumpComponent = findComponentById(ResourceTable.Id_jump_to_next_page_button); + jumpComponent.setClickedListener(component -> jumpOnline()); + // init the avBrowser and connect the browser service + ElementName elementName = new ElementName("", AVPlayService.class.getPackage().getName(), + AVPlayService.class.getName()); + avBrowser = new AVBrowser(this, elementName, avConnectionCallback, null); + connectService(); + } + + private void jumpOnline() { + Intent intent = new Intent(); + Operation operation = new Intent.OperationBuilder().withBundleName( + OnlinePlayClient.class.getPackage().getName()).withAbilityName(OnlinePlayClient.class.getName()).build(); + intent.setOperation(operation); + startAbility(intent); + } + + @Override + protected void onStop() { + LogUtil.info(TAG, "onStop"); + super.onStop(); + Intent intent = new Intent(); + Operation operation = new Intent.OperationBuilder().withBundleName(AVPlayService.class.getPackage().getName()) + .withAbilityName(AVPlayService.class.getName()) + .build(); + intent.setOperation(operation); + stopAbility(intent); + + if (avBrowser != null && avBrowser.isConnected()) { + avBrowser.disconnect(); + } + } + + /** + * create the list of audios + */ + class AVElementItemProvider extends RecycleItemProvider { + @Override + public int getCount() { + return avElementList.size(); + } + + @Override + public Object getItem(int position) { + return avElementList.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public Component getComponent(int position, Component component, ComponentContainer componentContainer) { + final Object item = avElementList.get(position); + if (component == null && item instanceof AVElement) { + String itemText = ((AVElement) item).getAVDescription().getTitle().toString(); + Text text = (Text) LayoutScatter.getInstance(AVPlayClient.this) + .parse(ResourceTable.Layout_list_item, null, false); + text.setText(itemText); + return text; + } else { + return component; + } + } + }; + + private AVConnectionCallback avConnectionCallback = new AVConnectionCallback() { + @Override + public void onConnected() { + LogUtil.info(TAG + "-AVConnectionCallback", "onConnected"); + // before subscribe, first to unsubscribe, it's necessary. + avBrowser.unsubscribeByParentMediaId(AVPlayService.PARENT_MEDIA_ID_1); + // onLoadAVElementList() on service will be called if subscribed. + avBrowser.subscribeByParentMediaId(AVPlayService.PARENT_MEDIA_ID_1, avSubscriptionCallback); + } + + @Override + public void onDisconnected() { + LogUtil.warn(TAG + "-AVConnectionCallback", "onDisconnected"); + } + + @Override + public void onConnectFailed() { + LogUtil.error(TAG + "-AVConnectionCallback", "onConnectionFailed"); + } + }; + + private AVControllerCallback avControllerCallback = new AVControllerCallback() { + @Override + public void onAVMetadataChanged(AVMetadata metadata) { + // this will be called with avSession.setAVMetadata() called on service. + super.onAVMetadataChanged(metadata); + LogUtil.info(TAG + "-AVControllerCallback", "onAVMetadataChanged"); + titleText.setText(metadata.getString(AVMetadata.AVTextKey.TITLE)); + totalTimeText.setText(String.valueOf(metadata.getLong(AVMetadata.AVLongKey.DURATION) / 1000)); + } + + @Override + public void onAVPlaybackStateChanged(AVPlaybackState playbackState) { + // this will be called with avSession.setAVPlaybackState() called on service. + super.onAVPlaybackStateChanged(playbackState); + LogUtil.info(TAG + "-AVControllerCallback", "onAVPlaybackStateChanged"); + switch (playbackState.getAVPlaybackState()) { + case AVPlaybackState.PLAYBACK_STATE_NONE: + case AVPlaybackState.PLAYBACK_STATE_PAUSED: { + playButton.setText(ResourceTable.String_play); + break; + } + case AVPlaybackState.PLAYBACK_STATE_PLAYING: { + playButton.setText(ResourceTable.String_pause); + progressText.setText(String.valueOf(playbackState.getCurrentPosition() / 1000)); + break; + } + default: + break; + } + } + + @Override + public void onAVQueueChanged(List queue) { + // this will be called with avSession.setAVQueue() called on service. + super.onAVQueueChanged(queue); + LogUtil.info(TAG + "-AVControllerCallback", "onAVQueueChanged"); + } + + @Override + public void onAVSessionEvent(String event, PacMap extras) { + // this will be called with avSession.sendAVSessionEvent() called on service. + super.onAVSessionEvent(event, extras); + LogUtil.info(TAG + "-AVControllerCallback", "onAVSessionEvent"); + } + + @Override + public void onAVSessionDestroyed() { + // this will be called with avSession.release() called on service. + super.onAVSessionDestroyed(); + LogUtil.info(TAG + "-AVControllerCallback", "onAVSessionDestroyed"); + } + + @Override + public void onAVQueueTitleChanged(CharSequence title) { + // this will be called with avSession.setAVQueueTitle() called on service. + super.onAVQueueTitleChanged(title); + LogUtil.info(TAG + "-AVControllerCallback", "onAVQueueTitleChanged"); + } + + @Override + public void onOptionsChanged(PacMap extras) { + // this will be called with avSession.setOptions() called on service. + super.onOptionsChanged(extras); + LogUtil.info(TAG + "-AVControllerCallback", "onOptionsChanged"); + } + }; + + private AVSubscriptionCallback avSubscriptionCallback = new AVSubscriptionCallback() { + @Override + public void onAVElementListLoaded(String parentId, List children) { + // this will be called with onLoadAVElementList() called on service. + super.onAVElementListLoaded(parentId, children); + LogUtil.info(TAG + "-AVSubscriptionCallback", "onAVElementListLoaded, parentId=" + parentId); + if (parentId.equals(AVPlayService.PARENT_MEDIA_ID_1) && children != null && children.size() > 0) { + if (children.size() > 0) { + avElementList = children; + avElementsListItemProvider.notifyDataChanged(); + if (avController == null) { + avController = new AVController(AVPlayClient.this, avBrowser.getAVToken()); + avController.setAVControllerCallback(avControllerCallback); + } + previousButton.setEnabled(true); + nextButton.setEnabled(true); + playButton.setEnabled(true); + } + } + } + + @Override + public void onAVElementListLoaded(String parentId, List children, PacMap options) { + super.onAVElementListLoaded(parentId, children, options); + LogUtil.info(TAG + "-AVSubscriptionCallback", "onAVElementListLoaded"); + } + + @Override + public void onError(String parentId) { + super.onError(parentId); + LogUtil.error(TAG + "-AVSubscriptionCallback", "onError"); + } + + @Override + public void onError(String parentMediaId, PacMap options) { + super.onError(parentMediaId, options); + } + }; + + // to play the next audio in the avElements list + private void handlerNextEvent() { + LogUtil.info(TAG, "handlerNextEvent:" + avController.getAVPlaybackState()); + avController.getPlayControls().playNext(); + } + + // to play the previous audio in the avElements list + private void handlerPrevEvent() { + LogUtil.info(TAG, "handlerPrevEvent:" + avController.getAVPlaybackState()); + avController.getPlayControls().playPrevious(); + } + + // to play or pause the audio in the avElements list + private void handlerPlayEvent() { + LogUtil.info(TAG, "handlerPlayEvent:" + avController.getAVPlaybackState()); + switch (avController.getAVPlaybackState().getAVPlaybackState()) { + case AVPlaybackState.PLAYBACK_STATE_NONE: { + if (avElementList.size() > 0) { + Uri mediaUri = avElementList.get(0).getAVDescription().getMediaUri(); + avController.getPlayControls().playByUri(mediaUri, new PacMap()); + } + break; + } + case AVPlaybackState.PLAYBACK_STATE_PLAYING: { + avController.getPlayControls().pause(); + break; + } + case AVPlaybackState.PLAYBACK_STATE_PAUSED: { + avController.getPlayControls().play(); + break; + } + default: + break; + } + } + + private void connectService() { + if (verifySelfPermission(SystemPermission.READ_USER_STORAGE) == IBundleManager.PERMISSION_GRANTED) { + connectToBrowserService(); + } else { + requestPermissionsFromUser(new String[] {SystemPermission.READ_USER_STORAGE}, 0); + } + } + + @Override + public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) { + if (permissions == null || permissions.length == 0 || grantResults == null || grantResults.length == 0) { + return; + } + if (requestCode == 0) { + if (grantResults[0] == IBundleManager.PERMISSION_DENIED) { + terminateAbility(); + } else { + connectToBrowserService(); + } + } + } + + private void connectToBrowserService() { + try { + if (!avBrowser.isConnected()) { + avBrowser.connect(); + } + } catch (IllegalStateException exception) { + LogUtil.error(TAG, "connect to browser service repeat"); + } + } +} diff --git a/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayService.java b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayService.java new file mode 100644 index 0000000000..b1d7398d87 --- /dev/null +++ b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/AVPlayService.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.samples.audioplayer; + +import ohos.samples.audioplayer.model.AVElementManager; +import ohos.samples.audioplayer.utils.LogUtil; + +import ohos.aafwk.content.Intent; +import ohos.event.notification.NotificationRequest; +import ohos.media.common.AVDescription; +import ohos.media.common.AVMetadata; +import ohos.media.common.Source; +import ohos.media.common.sessioncore.AVBrowserResult; +import ohos.media.common.sessioncore.AVBrowserRoot; +import ohos.media.common.sessioncore.AVPlaybackState; +import ohos.media.common.sessioncore.AVSessionCallback; +import ohos.media.player.Player; +import ohos.media.sessioncore.AVBrowserService; +import ohos.media.sessioncore.AVSession; +import ohos.utils.PacMap; +import ohos.utils.net.Uri; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * The service of the player, that base on AVBrowserService. + */ +public class AVPlayService extends AVBrowserService { + private static final String TAG = AVPlayService.class.getSimpleName(); + + /** + * parent media id 1 + */ + public static final String PARENT_MEDIA_ID_1 = "PARENT_MEDIA_ID_1"; + + /** + * parent media id 2 + */ + public static final String PARENT_MEDIA_ID_2 = "PARENT_MEDIA_ID_2"; + + private static final int NOTIFICATION_ID = 1005; + + private static final String NOTIFICATION_TITLE = "audioPlayer"; + + private static final String NOTIFICATION_TEXT = "audioPlayer is running"; + + private static final int TIME_DELAY = 500; + + private static final int TIME_LOOP = 1000; + + private AVElementManager avElementManager; + + private AVSession avSession; + + private Player player; + + private Timer timer = new Timer(); + + private ProgressTimerTask progressTimerTask; + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + LogUtil.info(TAG, "onStart"); + avElementManager = new AVElementManager(getApplicationContext()); + + AVPlaybackState avPlaybackState = new AVPlaybackState.Builder().setAVPlaybackState( + AVPlaybackState.PLAYBACK_STATE_NONE, 0, 1.0f).build(); + + avSession = new AVSession(getApplicationContext(), AVPlayService.class.getName()); + avSession.setAVSessionCallback(avSessionCallback); + avSession.setAVPlaybackState(avPlaybackState); + setAVToken(avSession.getAVToken()); + + player = new Player(getApplicationContext()); + // create notification and 1005 is notificationId + NotificationRequest request = new NotificationRequest(NOTIFICATION_ID); + NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent(); + content.setTitle(NOTIFICATION_TITLE).setText(NOTIFICATION_TEXT); + NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent( + content); + request.setContent(notificationContent); + keepBackgroundRunning(NOTIFICATION_ID, request); + } + + @Override + public void onStop() { + super.onStop(); + // this service will be cancel background running after this is called + cancelBackgroundRunning(); + LogUtil.info(TAG, "onDestroy"); + if (player != null) { + player.release(); + player = null; + } + if (avSession != null) { + avSession.setAVSessionCallback(null); + avSession.release(); + avSession = null; + } + if (progressTimerTask != null) { + progressTimerTask.cancel(); + progressTimerTask = null; + } + } + + @Override + public AVBrowserRoot onGetRoot(String clientPackageName, int clientUid, PacMap rootHints) { + LogUtil.info(TAG, "onGetRoot"); + return new AVBrowserRoot(PARENT_MEDIA_ID_1, null); + } + + @Override + public void onLoadAVElementList(String parentId, AVBrowserResult result) { + LogUtil.info(TAG, "onLoadAVElementList"); + result.detachForRetrieveAsync(); + switch (parentId) { + case PARENT_MEDIA_ID_1: { + result.sendAVElementList(avElementManager.getAvQueueElements()); + break; + } + case PARENT_MEDIA_ID_2: + default: + break; + } + } + + @Override + public void onLoadAVElementList(String parentId, AVBrowserResult avBrowserResult, PacMap pacMap) { + LogUtil.info(TAG, "onLoadAVElementList-2"); + } + + @Override + public void onLoadAVElement(String parentId, AVBrowserResult avBrowserResult) { + LogUtil.info(TAG, "onLoadAVElement"); + } + + private AVSessionCallback avSessionCallback = new AVSessionCallback() { + @Override + public void onPlay() { + super.onPlay(); + LogUtil.info(TAG + "-AVSessionCallback", "onPlay"); + if (avSession.getAVController().getAVPlaybackState().getAVPlaybackState() + == AVPlaybackState.PLAYBACK_STATE_PAUSED) { + player.play(); + AVPlaybackState avPlaybackState = new AVPlaybackState.Builder().setAVPlaybackState( + AVPlaybackState.PLAYBACK_STATE_PLAYING, player.getCurrentTime(), player.getCurrentTime()).build(); + avSession.setAVPlaybackState(avPlaybackState); + startProgressTaskTimer(); + } + } + + @Override + public void onPause() { + super.onPause(); + LogUtil.info(TAG + "-AVSessionCallback", "onPause"); + if (avSession.getAVController().getAVPlaybackState().getAVPlaybackState() + == AVPlaybackState.PLAYBACK_STATE_PLAYING) { + player.pause(); + AVPlaybackState avPlaybackState = new AVPlaybackState.Builder().setAVPlaybackState( + AVPlaybackState.PLAYBACK_STATE_PAUSED, player.getCurrentTime(), player.getPlaybackSpeed()).build(); + avSession.setAVPlaybackState(avPlaybackState); + } + } + + @Override + public void onPlayNext() { + super.onPlayNext(); + LogUtil.info(TAG + "-AVSessionCallback", "onPlayNext"); + AVDescription next = avElementManager.getNextAVElement().get().getAVDescription(); + play(next, 0); + } + + @Override + public void onPlayPrevious() { + super.onPlayPrevious(); + LogUtil.info(TAG + "-AVSessionCallback", "onPlayPrevious"); + AVDescription previous = avElementManager.getPreviousAVElement().get().getAVDescription(); + play(previous, 0); + } + + private void play(AVDescription description, int position) { + if (player == null) { + player = new Player(getApplicationContext()); + } + player.reset(); + player.setSource(new Source(description.getMediaUri().toString())); + player.prepare(); + player.rewindTo(position); + player.play(); + + AVPlaybackState avPlaybackState = new AVPlaybackState.Builder().setAVPlaybackState( + AVPlaybackState.PLAYBACK_STATE_PLAYING, player.getCurrentTime(), + player.getPlaybackSpeed()).build(); + avSession.setAVPlaybackState(avPlaybackState); + avSession.setAVMetadata(getAVMetadata(description)); + startProgressTaskTimer(); + } + + private AVMetadata getAVMetadata(AVDescription description) { + PacMap extrasPacMap = description.getExtras(); + return new AVMetadata.Builder().setString(AVMetadata.AVTextKey.TITLE, description.getTitle().toString()) + .setLong(AVMetadata.AVLongKey.DURATION, extrasPacMap.getLongValue(AVMetadata.AVLongKey.DURATION)) + .setString(AVMetadata.AVTextKey.META_URI, description.getMediaUri().toString()) + .build(); + } + + private void startProgressTaskTimer() { + if (progressTimerTask != null) { + progressTimerTask.cancel(); + } + progressTimerTask = new ProgressTimerTask(); + timer.schedule(progressTimerTask, TIME_DELAY, TIME_LOOP); + } + + @Override + public void onPlayByUri(Uri uri, PacMap extras) { + LogUtil.info(TAG + "-AVSessionCallback", "onPlayByUri"); + switch (avSession.getAVController().getAVPlaybackState().getAVPlaybackState()) { + case AVPlaybackState.PLAYBACK_STATE_PAUSED: + case AVPlaybackState.PLAYBACK_STATE_NONE: { + avElementManager.setCurrentAVElement(uri); + AVDescription current = avElementManager.getCurrentAVElement().getAVDescription(); + play(current, 0); + break; + } + default: + break; + } + } + + @Override + public void onPlayBySearch(String query, PacMap extras) { + LogUtil.info(TAG + "-AVSessionCallback", "onPlayBySearch"); + } + + @Override + public void onSetAVPlaybackCustomAction(String action, PacMap extras) { + super.onSetAVPlaybackCustomAction(action, extras); + LogUtil.info(TAG + "-AVSessionCallback", "onSetAVPlaybackCustomAction"); + } + }; + + // used to get the playing status in period + class ProgressTimerTask extends TimerTask { + @Override + public void run() { + if (avSession.getAVController().getAVPlaybackState().getAVPlaybackState() + == AVPlaybackState.PLAYBACK_STATE_PLAYING) { + AVPlaybackState avPlaybackState = new AVPlaybackState.Builder().setAVPlaybackState( + AVPlaybackState.PLAYBACK_STATE_PLAYING, player.getCurrentTime(), player.getPlaybackSpeed()).build(); + avSession.setAVPlaybackState(avPlaybackState); + } + } + } +} diff --git a/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/OnlinePlayClient.java b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/OnlinePlayClient.java new file mode 100644 index 0000000000..e6adb6bbf8 --- /dev/null +++ b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/OnlinePlayClient.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.samples.audioplayer; + +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; +import ohos.agp.components.Button; +import ohos.agp.components.Text; +import ohos.media.common.Source; +import ohos.media.player.Player; +import ohos.samples.audioplayer.utils.LogUtil; +import ohos.samples.audioplayer.utils.ThreadPoolManager; + +/** + * OtherWayToPlay class + */ +public class OnlinePlayClient extends Ability { + private static final String TAG = OnlinePlayClient.class.getSimpleName(); + + private static final String WEB_PATH = "https://ss0.bdstatic.com/-0U0bnSm1A5BphGlnYG/" + + "cae-legoup-video-target/93be3d88-9fc2-4fbd-bd14-833bca731ca7.mp4"; + + private Player player; + private Button playButton; + private Runnable playRunnable; + private boolean isPrepared; + + @Override + protected void onStart(Intent intent) { + super.onStart(intent); + setUIContent(ResourceTable.Layout_online_play_layout); + + initMedia(); + } + + private void initMedia() { + player = new Player(getApplicationContext()); + playRunnable = new PlayRunnable(); + ThreadPoolManager.getInstance().execute(playRunnable); + } + + @Override + protected void onActive() { + super.onActive(); + + Text uriText = (Text) findComponentById(ResourceTable.Id_input_uri); + playButton = (Button) findComponentById(ResourceTable.Id_play_button); + uriText.setText(WEB_PATH); + playButton.setClickedListener(component -> playOrPause()); + } + + private void playOrPause() { + if (player == null) { + LogUtil.warn(TAG, "player is null."); + return; + } + + if (player.isNowPlaying()) { + pause(); + return; + } + play(); + } + + private void pause() { + player.pause(); + playButton.setText(ResourceTable.String_play); + } + + private void play() { + if (!isPrepared) { + LogUtil.warn(TAG, "prepare failed"); + return; + } + if (!player.play()) { + LogUtil.warn(TAG, "play failed"); + return; + } + playButton.setText(ResourceTable.String_pause); + } + + private class PlayRunnable implements Runnable { + @Override + public void run() { + Source source = new Source(WEB_PATH); + if (!player.setSource(source)) { + LogUtil.warn(TAG, "uri is invalid"); + return; + } + isPrepared = player.prepare(); + } + } + + @Override + protected void onInactive() { + super.onInactive(); + pause(); + } + + @Override + protected void onStop() { + super.onStop(); + release(); + } + + private void release() { + if (player != null) { + player.stop(); + player.release(); + } + ThreadPoolManager.getInstance().cancel(playRunnable); + } +} diff --git a/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/model/AVElementManager.java b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/model/AVElementManager.java new file mode 100644 index 0000000000..b1e2bb677a --- /dev/null +++ b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/model/AVElementManager.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.samples.audioplayer.model; + +import ohos.samples.audioplayer.utils.LogUtil; + +import ohos.aafwk.ability.DataAbilityHelper; +import ohos.aafwk.ability.DataAbilityRemoteException; +import ohos.app.Context; +import ohos.data.resultset.ResultSet; +import ohos.media.common.AVDescription; +import ohos.media.common.AVMetadata; +import ohos.media.common.sessioncore.AVElement; +import ohos.media.photokit.metadata.AVStorage; +import ohos.utils.PacMap; +import ohos.utils.net.Uri; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * This class is used to prepare audio files for applications. + */ +public class AVElementManager { + private static final String TAG = AVElementManager.class.getSimpleName(); + + private List avElements = new ArrayList<>(); + + private AVElement current; + + /** + * The construction method of this class + * + * @param context Context + */ + public AVElementManager(Context context) { + loadFromMediaLibrary(context); + } + + private void loadFromMediaLibrary(Context context) { + Uri remoteUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI; + DataAbilityHelper helper = DataAbilityHelper.creator(context, remoteUri, false); + try { + ResultSet resultSet = helper.query(remoteUri, null, null); + LogUtil.info(TAG, "The result size: " + resultSet.getRowCount()); + processResult(resultSet); + resultSet.close(); + } catch (DataAbilityRemoteException e) { + LogUtil.error(TAG, "Query system media failed."); + } finally { + helper.release(); + } + } + + 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)); + long duration = resultSet.getInt(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DURATION)); + LogUtil.info(TAG, "Add new video file: " + path); + PacMap pacMap = new PacMap(); + pacMap.putLongValue(AVMetadata.AVLongKey.DURATION, duration); + AVDescription bean = new AVDescription.Builder().setTitle(title) + .setIMediaUri(Uri.parse(path)) + .setMediaId(path) + .setExtras(pacMap) + .build(); + avElements.add(new AVElement(bean, AVElement.AVELEMENT_FLAG_PLAYABLE)); + } + setDefaultAVElement(); + } + + private void setDefaultAVElement() { + if (avElements.size() > 0) { + current = avElements.get(0); + } + } + + /** + * get the list of avElements + * + * @return avElements the list of avElements + */ + public List getAvQueueElements() { + return avElements; + } + + /** + * set the current AVElement by uri + * + * @param uri uri of item + * @return true if set success, else false + */ + public boolean setCurrentAVElement(Uri uri) { + for (AVElement element : avElements) { + if (element.getAVDescription().getMediaUri().toString().equals(uri.toString())) { + current = element; + return true; + } + } + setDefaultAVElement(); + return false; + } + + /** + * get the current AVElement + * + * @return AVElement the current AVElement + */ + public AVElement getCurrentAVElement() { + return current; + } + + /** + * get the next AVElement + * + * @return AVElement the next AVElement + */ + public Optional getNextAVElement() { + for (int i = 0; i < avElements.size(); i++) { + if (avElements.get(i).equals(current)) { + int index = i + 1; + current = avElements.get(index < avElements.size() ? index : 0); + return Optional.of(current); + } + } + setDefaultAVElement(); + return Optional.of(current); + } + + /** + * get the previous AVElement + * + * @return AVElement the previous AVElement + */ + public Optional getPreviousAVElement() { + for (int i = 0; i < avElements.size(); i++) { + if (avElements.get(i).equals(current)) { + int index = i - 1; + current = avElements.get(index >= 0 ? index : avElements.size() - 1); + return Optional.of(current); + } + } + setDefaultAVElement(); + return Optional.of(current); + } +} diff --git a/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/LogUtil.java b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/LogUtil.java new file mode 100644 index 0000000000..0828d52cba --- /dev/null +++ b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/LogUtil.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.samples.audioplayer.utils; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * Log utils + */ +public class LogUtil { + private static final String TAG_LOG = "AVPlayer"; + + private static final int DOMAIN_ID = 0xD000F00; + + private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, DOMAIN_ID, LogUtil.TAG_LOG); + + private static final String LOG_FORMAT = "%{public}s: %{public}s"; + + private LogUtil() { + } + + /** + * Print debug log + * + * @param tag log tag + * @param msg log message + */ + public static void debug(String tag, String msg) { + HiLog.debug(LABEL_LOG, LOG_FORMAT, tag, msg); + } + + /** + * Print info log + * + * @param tag log tag + * @param msg log message + */ + public static void info(String tag, String msg) { + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, msg); + } + + /** + * Print warn log + * + * @param tag log tag + * @param msg log message + */ + public static void warn(String tag, String msg) { + HiLog.warn(LABEL_LOG, LOG_FORMAT, tag, msg); + } + + /** + * Print error log + * + * @param tag log tag + * @param msg log message + */ + public static void error(String tag, String msg) { + HiLog.error(LABEL_LOG, LOG_FORMAT, tag, msg); + } +} diff --git a/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/ThreadPoolManager.java b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/ThreadPoolManager.java new file mode 100644 index 0000000000..fbbcd2f20f --- /dev/null +++ b/media/AudioPlayer/entry/src/main/java/ohos/samples/audioplayer/utils/ThreadPoolManager.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.samples.audioplayer.utils; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Thread pool manager. + */ +public class ThreadPoolManager { + private static final int CPU_COUNT = 20; + + private static final int CORE_POOL_SIZE = CPU_COUNT + 1; + + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + + private static final int KEEP_ALIVE = 1; + + private static ThreadPoolManager instance; + + private ThreadPoolExecutor executor; + + private ThreadPoolManager() { + if (executor == null) { + executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, + new ArrayBlockingQueue(20), Executors.defaultThreadFactory(), + new ThreadPoolExecutor.AbortPolicy()); + } + } + + /** + * Create ThreadPoolManager object. + * + * @return ThreadPoolManager. + */ + public static synchronized ThreadPoolManager getInstance() { + if (instance == null) { + synchronized (ThreadPoolManager.class) { + if (instance == null) { + instance = new ThreadPoolManager(); + } + } + } + return instance; + } + + /** + * Start a thread that does not return any information. + * + * @param runnable Runnable. + */ + public void execute(Runnable runnable) { + executor.execute(runnable); + } + + /** + * Remove runnable. + * + * @param runnable Runnable. + */ + public void cancel(Runnable runnable) { + if (runnable != null) { + executor.getQueue().remove(runnable); + } + } +} \ No newline at end of file diff --git a/media/AudioPlayer/entry/src/main/resources/base/element/string.json b/media/AudioPlayer/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000..dacfceeb43 --- /dev/null +++ b/media/AudioPlayer/entry/src/main/resources/base/element/string.json @@ -0,0 +1,32 @@ +{ + "string": [ + { + "name": "app_name", + "value": "AudioPlayer" + }, + { + "name": "mainability_description", + "value": "hap sample empty page" + }, + { + "name": "to_previous", + "value": "<" + }, + { + "name": "play", + "value": "PLAY" + }, + { + "name": "to_next", + "value": ">" + }, + { + "name": "pause", + "value": "||" + }, + { + "name": "jump", + "value": "TO PLAY ONLINE AUDIO" + } + ] +} \ No newline at end of file diff --git a/media/AudioPlayer/entry/src/main/resources/base/graphic/list_item_background.xml b/media/AudioPlayer/entry/src/main/resources/base/graphic/list_item_background.xml new file mode 100644 index 0000000000..9ff58ae313 --- /dev/null +++ b/media/AudioPlayer/entry/src/main/resources/base/graphic/list_item_background.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/media/AudioPlayer/entry/src/main/resources/base/layout/list_item.xml b/media/AudioPlayer/entry/src/main/resources/base/layout/list_item.xml new file mode 100644 index 0000000000..96569b23eb --- /dev/null +++ b/media/AudioPlayer/entry/src/main/resources/base/layout/list_item.xml @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/media/AudioPlayer/entry/src/main/resources/base/layout/main_play_layout.xml b/media/AudioPlayer/entry/src/main/resources/base/layout/main_play_layout.xml new file mode 100644 index 0000000000..05280bbaff --- /dev/null +++ b/media/AudioPlayer/entry/src/main/resources/base/layout/main_play_layout.xml @@ -0,0 +1,131 @@ + + + + +