diff --git a/README.md b/README.md index cc97540807217b5100128e5732688c9771a3086c..aa50d07a98bbadd2893f81807af6eb29cf404076 100755 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ We provide a series of app samples to help you quickly get familiar with the API 3. The following app samples can run on OpenHarmony, and other app samples can run only on HarmonyOS: - common/Clock - common/JsHelloWorld + - ability/JsDistributedMusicPlayer ## Repositories Involved diff --git a/README_zh.md b/README_zh.md index c8fef39b3fb170cf3d0f4003758cade7a3ed6838..97181dbd694484e08fdc25fc848f90680f527155 100755 --- a/README_zh.md +++ b/README_zh.md @@ -22,6 +22,7 @@ 3. 以下路径下的应用示例支持在OpenHarmony上运行,其余应用示例仅支持在HarmonyOS上运行。 - common/Clock - common/JsHelloWorld + - ability/JsDistributedMusicPlayer ## 相关仓 diff --git a/ability/JsDistributedMusicPlayer/README_zh.md b/ability/JsDistributedMusicPlayer/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..e89388ad447f96e0c3a8762938cb5eb3bd8930d5 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/README_zh.md @@ -0,0 +1,24 @@ +# DistributedMusicPlayer + +本示例完成了基本的音乐播放、暂停、上一曲、下一曲功能,并使用分布式能力完成了音乐播放状态的跨设备迁移。 +- **音乐播放** + + 使用MediaLibrary完成本地媒体文件扫描,并通过AudioPlayer完成了音乐的播放。 +- **跨设备迁移播放** + + 使用DeviceManager完成了分布式设备列表的显示 + + 使用分布式调度以及分布式数据完成了跨设备迁移功能 + +## 相关仓 +应用子系统 + +multimedia_medialibrary_standard + +multimedia_media_standard + +device_manager + +distributedschedule_dms_fwk + +distributeddatamgr_datamgr \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/build.gradle b/ability/JsDistributedMusicPlayer/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b7a0757747cb0b43d59783fc5c71d439d3915624 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/build.gradle @@ -0,0 +1,49 @@ + +/* + * 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 6 + defaultConfig { + compatibleSdkVersion 6 + } +} +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.3-RC' + } +} +allprojects { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + jcenter() + } +} diff --git a/ability/JsDistributedMusicPlayer/entry/build.gradle b/ability/JsDistributedMusicPlayer/entry/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..921386f38e8d5e8e0272b5326c80b80bcf5a067d --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/build.gradle @@ -0,0 +1,16 @@ +apply plugin: 'com.huawei.ohos.hap' +ohos { + compileSdkVersion 6 + defaultConfig { + compatibleSdkVersion 6 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + supportSystem "standard" +} diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/config.json b/ability/JsDistributedMusicPlayer/entry/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..1f0b159296010226ea0dc749d5b7e26104b859e9 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/config.json @@ -0,0 +1,62 @@ +{ + "app": { + "bundleName": "com.ohos.distributedmusicplayer", + "version": { + "code": 1000000, + "name": "1.0" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.ohos.distributedmusicplayer", + "name": ".MyApplication", + "mainAbility": "com.ohos.distributedmusicplayer.MainAbility", + "reqCapabilities": [ + "video_support" + ], + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry", + "installationFree": false + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "visible": true, + "formsEnabled": false, + "name": "com.ohos.distributedmusicplayer.MainAbility", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + } + ], + "js": [ + { + "pages": [ + "pages/index/index" + ], + "name": "default", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/default/app.js b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/app.js new file mode 100644 index 0000000000000000000000000000000000000000..5e867767cee4cd5e3dd75300023c8c3da2274455 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/app.js @@ -0,0 +1,23 @@ +/* + * 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. + */ + +export default { + onCreate() { + console.info('MusicPlayer[Application] onCreate'); + }, + onDestroy() { + console.info('MusicPlayer[Application] onDestroy'); + } +}; diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/default/i18n/en-US.json b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/i18n/en-US.json new file mode 100644 index 0000000000000000000000000000000000000000..5ded2a9fdbc806e0e339635211c7d7aad0c163af --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/i18n/en-US.json @@ -0,0 +1,7 @@ +{ + "strings": { + "localhost": "This device" + }, + "Files": { + } +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/default/i18n/zh-CN.json b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/i18n/zh-CN.json new file mode 100644 index 0000000000000000000000000000000000000000..e95430968e4dd842a83badd5743ebcf0df2afd1d --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/i18n/zh-CN.json @@ -0,0 +1,7 @@ +{ + "strings": { + "localhost": "本机" + }, + "Files": { + } +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.css b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.css new file mode 100644 index 0000000000000000000000000000000000000000..e613fd8832e46e7f313ea216b7b405621d450d7c --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.css @@ -0,0 +1,168 @@ +/* + * 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. + */ + +.container { + flex-direction: column; + justify-content: space-between; + align-items: center; + background-image: url(common/media/bg_blurry.png); + background-size: cover; + background-position: center center; + padding-start: 64px; + padding-end: 64px; +} + +.title_section { + margin-top: 150px; + margin-bottom: 10px; + flex-direction: row; + justify-content: center; +} + +.title { + height: 64px; + font-size: 48px; + color: #FFF; + margin-bottom: 10px; + text-align: center; +} + +.album_section { + width: 100%; + aspect-ratio: 1; + flex-direction: row; + align-items: center; + margin-top: 25px; + margin-bottom: 25px; +} + +.album_image { + align-items: center; + object-fit: contain; +} + +.progress_section { + margin-bottom: 100px; + flex-direction: column; +} + +.timer { + flex-direction: row; + justify-content: space-between; + margin-bottom: 12px; +} + +.progress_time { + height: 32px; + color: #FFF; + text-align: center; + font-size: 24px; +} + +.total_time { + height: 32px; + color: #FFF; + text-align: center; + font-size: 24px; +} + +.music_slider { + width: 100%; + color: #64CCE7FF; + padding-left: 0; + padding-right: 0; +} + +.control_section { + width: 100%; + justify-content: space-between; + flex-direction: row; +} + +.control_button { + height: 96px; + width: 96px; +} + +.txt { + color: #000; + font-weight: bold; + font-size: 39px; +} + +.dialog-main { + width: 500px; +} + +.dialog-div { + flex-direction: column; + align-items: center; +} + +.dialog_title_text { + width: 434px; + height: 80px; + font-size: 32px; + font-weight: 600; +} + +.inner-btn { + width: 400px; + height: 120px; + justify-content: space-around; + align-items: center; +} + +.dialog_cancel_button { + width: 100%; + font-size: 32px; +} + +.dialog_device_list { + width: 434px; + max-height: 150px; +} + +.device_list_item { + width: 434px; + height: 80px; + flex-direction: row; + align-items: center; +} + +.device_item_radio { +} + +.device_item_title { + width: 80%; + height: 80px; + text-align: start; +} + +@media screen and (device-type: tablet) and (orientation: landscape) { + +} + +@media screen and (device-type: wearable) { + +} + +@media screen and (device-type: tv) { + +} + +@media screen and (device-type: phone) and (orientation: landscape) { + +} diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.hml b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.hml new file mode 100644 index 0000000000000000000000000000000000000000..63ecf4bba9228d142baf75ab53dee9b078132e23 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.hml @@ -0,0 +1,59 @@ + + +
+
+ + {{ title }} + +
+
+ + +
+
+
+ {{ currentTimeText }} + + {{ totalTimeText }} + +
+ +
+
+ + + + +
+ +
+ 选择设备 + + + + + + +
+ +
+
+
+
diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.js b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.js new file mode 100644 index 0000000000000000000000000000000000000000..aba10c29dd0779a39c4546368ad9181c5dfb152d --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/default/pages/index/index.js @@ -0,0 +1,285 @@ +/* + * 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. + */ + +import featureAbility from '@ohos.ability.featureability'; +import RemoteDeviceModel from '../../../model/RemoteDeviceModel.js'; +import PlayerModel from '../../../model/PlayerModel.js'; +import KvStoreModel from '../../../model/KvStoreModel.js'; + +function getShownTimer(ms) { + var seconds = Math.floor(ms / 1000); + var sec = seconds % 60; + var min = (seconds - sec) / 60; + if (sec < 10) { + sec = '0' + sec; + } + if (min < 10) { + min = '0' + min; + } + return min + ':' + sec; +} + +const REMOTE_ABILITY_STARTED = 'remoteAbilityStarted'; +var DEVICE_LIST_LOCALHOST; + +export default { + data: { + title: '', + currentTimeText: '', + totalTimeText: '', + totalMs: 0, + currentProgress: 0, + deviceList: [], + btnPlaySrc: '/common/media/ic_play.svg', + albumSrc: '/common/media/album.png', + remoteDeviceModel: new RemoteDeviceModel(), + playerModel: new PlayerModel(), + kvStoreModel: new KvStoreModel(), + isDialogShowing: false, + isSwitching: false, + }, + onInit() { + console.info('MusicPlayer[IndexPage] onInit'); + DEVICE_LIST_LOCALHOST = { + name: this.$t('strings.localhost'), + id: 'localhost', + }; + this.deviceList = [DEVICE_LIST_LOCALHOST]; + let self = this; + this.playerModel.setOnStatusChangedListener((isPlaying) => { + console.info('MusicPlayer[IndexPage] on player status changed, isPlaying=' + isPlaying + ', refresh ui'); + self.playerModel.setOnPlayingProgressListener((currentTimeMs) => { + self.currentTimeText = getShownTimer(currentTimeMs); + self.currentProgress = Math.floor(currentTimeMs / self.totalMs * 100); + }); + if (isPlaying) { + self.btnPlaySrc = '/common/media/ic_pause.svg'; + } else { + self.btnPlaySrc = '/common/media/ic_play.svg'; + } + }); + this.playerModel.getPlaylist(() => { + console.info('MusicPlayer[IndexPage] on playlist generated, refresh ui'); + + featureAbility.getWant((error, want) => { + console.info('MusicPlayer[IndexPage] featureAbility.getWant=' + JSON.stringify(want)); + var status = want.parameters; + if (status) { + self.kvStoreModel.broadcastMessage(REMOTE_ABILITY_STARTED); + console.info('MusicPlayer[IndexPage] restorePlayingStatus'); + self.playerModel.restorePlayingStatus(status, (index) => { + console.info('MusicPlayer[IndexPage] restorePlayingStatus finished, index=' + index); + if (index >= 0) { + self.refreshSongInfo(index); + } else { + self.playerModel.preLoad(0, () => { + self.refreshSongInfo(0); + }); + } + }); + } else { + self.playerModel.preLoad(0, () => { + self.refreshSongInfo(0); + }); + } + }); + }); + }, + onBackPress() { + console.info('MusicPlayer[IndexPage] onBackPress isDialogShowing=' + this.isDialogShowing); + if (this.isDialogShowing === true) { + this.dismissDialog(); + return true; + } + return false; + }, + onDestroy() { + this.playerModel.release(); + this.remoteDeviceModel.unregisterDeviceListCallback(); + }, + refreshSongInfo(index) { + console.info('MusicPlayer[IndexPage] refreshSongInfo ' + index + '/' + + this.playerModel.playlist.audioFiles.length); + if (index >= this.playerModel.playlist.audioFiles.length) { + console.warn('MusicPlayer[IndexPage] refreshSongInfo ignored'); + return; + } + // update song title + this.title = this.playerModel.playlist.audioFiles[index].name; + this.albumSrc = (index % 2 === 0) ? '/common/media/album.png' : '/common/media/album2.png'; + + // update duration + this.totalMs = this.playerModel.getDuration(); + this.totalTimeText = getShownTimer(this.totalMs); + this.currentTimeText = getShownTimer(this.playerModel.getCurrentMs()); + this.currentProgress = Math.floor(this.playerModel.getCurrentMs() / this.totalMs * 100); + + console.info('MusicPlayer[IndexPage] refreshSongInfo this.title=' + this.title + ' this.totalMs=' + + this.totalMs + ' this.totalTimeText=' + this.totalTimeText + ' this.currentTimeText=' + this.currentTimeText); + }, + setProgress(e) { + console.info('MusicPlayer[IndexPage] setProgress ' + e.mode + ', ' + e.value); + this.currentProgress = e.value; + if (isNaN(this.totalMs)) { + this.currentProgress = 0; + console.info('MusicPlayer[IndexPage] setProgress ignored, totalMs=' + this.totalMs); + return; + } + var currentMs = this.currentProgress / 100 * this.totalMs; + this.currentTimeText = getShownTimer(currentMs); + if (e.mode === 'end' || e.mode === 'click') { + console.info('MusicPlayer[IndexPage] player.seek ' + currentMs); + this.playerModel.seek(currentMs); + } + }, + onPreviousClick() { + if (this.isSwitching) { + console.info('MusicPlayer[IndexPage] onPreviousClick ignored, isSwitching'); + return; + } + console.info('MusicPlayer[IndexPage] onPreviousClick'); + this.playerModel.index--; + if (this.playerModel.index < 0 && this.playerModel.playlist.audioFiles.length >= 1) { + this.playerModel.index = this.playerModel.playlist.audioFiles.length - 1; + } + this.currentProgress = 0; + this.isSwitching = true; + let self = this; + this.playerModel.preLoad(this.playerModel.index, () => { + self.refreshSongInfo(self.playerModel.index); + self.playerModel.play(0, true); + self.isSwitching = false; + }); + }, + onNextClick() { + if (this.isSwitching) { + console.info('MusicPlayer[IndexPage] onNextClick ignored, isSwitching'); + return; + } + console.info('MusicPlayer[IndexPage] onNextClick'); + this.playerModel.index++; + if (this.playerModel.index >= this.playerModel.playlist.audioFiles.length) { + this.playerModel.index = 0; + } + this.currentProgress = 0; + this.isSwitching = true; + let self = this; + this.playerModel.preLoad(this.playerModel.index, () => { + self.refreshSongInfo(self.playerModel.index); + self.playerModel.play(0, true); + self.isSwitching = false; + }); + }, + onPlayClick() { + if (this.isSwitching) { + console.info('MusicPlayer[IndexPage] onPlayClick ignored, isSwitching'); + return; + } + console.info('MusicPlayer[IndexPage] onPlayClick, isPlaying=' + this.playerModel.isPlaying); + if (this.playerModel.isPlaying) { + this.playerModel.pause(); + } else { + this.playerModel.preLoad(this.playerModel.index, () => { + this.playerModel.play(-1, true); + }); + } + }, + onContinueAbilityClick() { + console.info('MusicPlayer[IndexPage] onContinueAbilityClick'); + let self = this; + this.remoteDeviceModel.registerDeviceListCallback(() => { + console.info('MusicPlayer[IndexPage] registerDeviceListCallback, callback entered'); + var list = []; + list[0] = DEVICE_LIST_LOCALHOST; + var deviceList = self.remoteDeviceModel.deviceList; + console.info('MusicPlayer[IndexPage] on remote device updated, count=' + deviceList.length); + for (var i = 0; i < deviceList.length; i++) { + console.info('MusicPlayer[IndexPage] device ' + i + '/' + deviceList.length + ' deviceId=' + + deviceList[i].deviceId + ' deviceName=' + deviceList[i].deviceName + ' deviceType=' + + deviceList[i].deviceType); + list[i + 1] = { + name: deviceList[i].deviceName, + id: deviceList[i].deviceId, + }; + } + self.deviceList = list; + }); + this.$element('continueAbilityDialog').show(); + this.isDialogShowing = true; + }, + startAbilityContinuation(deviceId, deviceName) { + this.$element('continueAbilityDialog').close(); + var params; + if (this.playerModel.index >= 0 && this.playerModel.index <= this.playerModel.playlist.audioFiles.length) { + params = { + uri: this.playerModel.playlist.audioFiles[this.playerModel.index].fileUri, + seekTo: this.playerModel.getCurrentMs(), + isPlaying: this.playerModel.isPlaying + }; + } else { + params = { + uri: '', + seekTo: 0, + isPlaying: false + }; + } + console.info('MusicPlayer[IndexPage] featureAbility.startAbility deviceId=' + deviceId + + ' deviceName=' + deviceName); + var wantValue = { + bundleName: 'com.ohos.distributedmusicplayer', + abilityName: 'com.ohos.distributedmusicplayer.MainAbility', + deviceId: deviceId, + parameters: params + }; + featureAbility.startAbility({ + want: wantValue + }).then((data) => { + console.info('MusicPlayer[IndexPage] featureAbility.startAbility finished, ' + JSON.stringify(data)); + }); + console.info('MusicPlayer[IndexPage] featureAbility.startAbility want=' + JSON.stringify(wantValue)); + console.info('MusicPlayer[IndexPage] featureAbility.startAbility end'); + this.kvStoreModel.setOnMessageReceivedListener(REMOTE_ABILITY_STARTED, () => { + console.info('MusicPlayer[IndexPage] OnMessageReceived, terminateAbility'); + featureAbility.terminateAbility(); + }); + }, + onRadioChange(inputValue, e) { + console.info('MusicPlayer[IndexPage] onRadioChange ' + inputValue + ', ' + e.value); + if (inputValue === e.value) { + if (e.value === 'localhost') { + this.$element('continueAbilityDialog').close(); + return; + } + let self = this; + for (var i = 0; i < this.deviceList.length; i++) { + if (this.deviceList[i].id === e.value) { + this.startAbilityContinuation(this.deviceList[i].id, this.deviceList[i].name); + } + } + } + }, + cancelDialog(e) { + this.remoteDeviceModel.unregisterDeviceListCallback(); + this.isDialogShowing = false; + }, + onDismissDialogClicked(e) { + this.dismissDialog(); + }, + dismissDialog() { + this.$element('continueAbilityDialog').close(); + this.remoteDeviceModel.unregisterDeviceListCallback(); + this.isDialogShowing = false; + } +}; diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/model/KvStoreModel.js b/ability/JsDistributedMusicPlayer/entry/src/main/js/model/KvStoreModel.js new file mode 100644 index 0000000000000000000000000000000000000000..53875c47cdff3b1a4055d5617119971575653b28 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/model/KvStoreModel.js @@ -0,0 +1,114 @@ +/* + * 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. + */ + +import distributedData from '@ohos.data.distributeddata'; + +const STORE_ID = 'musicplayer_kvstore'; + +export default class KvStoreModel { + kvManager; + kvStore; + + constructor() { + } + + createKvStore(callback) { + if (typeof (this.kvStore) === 'undefined') { + var config = { + bundleName: 'com.ohos.distributedmusicplayer', + userInfo: { + userId: '0', + userType: 0 + } + }; + let self = this; + console.info('MusicPlayer[KvStoreModel] createKVManager begin'); + distributedData.createKVManager(config).then((manager) => { + console.info('MusicPlayer[KvStoreModel] createKVManager success, kvManager=' + JSON.stringify(manager)); + self.kvManager = manager; + var options = { + createIfMissing: true, + encrypt: false, + backup: false, + autoSync: true, + kvStoreType: 1, + schema: '', + securityLevel: 3, + }; + console.info('MusicPlayer[KvStoreModel] kvManager.getKVStore begin'); + self.kvManager.getKVStore(STORE_ID, options).then((store) => { + console.info('MusicPlayer[KvStoreModel] getKVStore success, kvStore=' + store); + self.kvStore = store; + callback(); + }); + console.info('MusicPlayer[KvStoreModel] kvManager.getKVStore end'); + }); + console.info('MusicPlayer[KvStoreModel] createKVManager end'); + } else { + callback(); + } + } + + broadcastMessage(msg) { + console.info('MusicPlayer[KvStoreModel] broadcastMessage ' + msg); + var num = Math.random(); + let self = this; + this.createKvStore(() => { + self.put(msg, num); + }); + } + + put(key, value) { + console.info('MusicPlayer[KvStoreModel] kvStore.put ' + key + '=' + value); + this.kvStore.put(key, value).then((data) => { + this.kvStore.get(key).then((data) => { + console.info('MusicPlayer[KvStoreModel] kvStore.get ' + key + '=' + JSON.stringify(data)); + }); + console.info('MusicPlayer[KvStoreModel] kvStore.put ' + key + ' finished, data=' + JSON.stringify(data)); + }).catch((err) => { + console.error('MusicPlayer[KvStoreModel] kvStore.put ' + key + ' failed, ' + JSON.stringify(err)); + }); + } + + setOnMessageReceivedListener(msg, callback) { + console.info('MusicPlayer[KvStoreModel] setOnMessageReceivedListener ' + msg); + let self = this; + this.createKvStore(() => { + console.info('MusicPlayer[KvStoreModel] kvStore.on(dataChange) begin'); + self.kvStore.on('dataChange', 1, (data) => { + console.info('MusicPlayer[KvStoreModel] dataChange, ' + JSON.stringify(data)); + console.info('MusicPlayer[KvStoreModel] dataChange, insert ' + data.insertEntries.length + ' udpate ' + + data.updateEntries.length); + for (var i = 0; i < data.insertEntries.length; i++) { + if (data.insertEntries[i].key === msg) { + console.info('MusicPlayer[KvStoreModel] insertEntries receive ' + msg + '=' + + JSON.stringify(data.insertEntries[i].value)); + callback(); + return; + } + } + for (i = 0; i < data.updateEntries.length; i++) { + if (data.updateEntries[i].key === msg) { + console.info('MusicPlayer[KvStoreModel] updateEntries receive ' + msg + '=' + + JSON.stringify(data.updateEntries[i].value)); + callback(); + return; + } + } + }); + console.info('MusicPlayer[KvStoreModel] kvStore.on(dataChange) end'); + }); + } +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/model/PlayerModel.js b/ability/JsDistributedMusicPlayer/entry/src/main/js/model/PlayerModel.js new file mode 100644 index 0000000000000000000000000000000000000000..8249050312772faa4cb29b31e2f081cbe839ff4a --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/model/PlayerModel.js @@ -0,0 +1,298 @@ +/* + * 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. + */ + +import media from '@ohos.multimedia.media'; +import mediaLibrary from '@ohos.multimedia.medialibrary'; + +export + +class Playlist { + constructor() { + } + + audioFiles = []; +} +export + +class Song { + constructor(name, fileUri, duration) { + this.name = name; + this.fileUri = fileUri; + this.duration = duration; + } +} +export default class PlayerModel { + isPlaying = false; + playlist = new Playlist; + index; + #player; + #statusChangedListener; + #playingProgressListener; + #intervalID; + #currentTimeMs = 0; + + constructor() { + this.#player = media.createAudioPlayer(); + console.info('MusicPlayer[PlayerModel] createAudioPlayer=' + this.#player); + } + + initAudioPlayer() { + console.info('MusicPlayer[PlayerModel] initAudioPlayer begin'); + this.#player.on('error', (err, action) => { + console.error(`MusicPlayer[PlayerModel] player error: ${err.code}`); + }); + let self = this; + this.#player.on('finish', (err, action) => { + if (err) { + console.error(`MusicPlayer[PlayerModel] error returned in finish() callback`); + return; + } + console.log('MusicPlayer[PlayerModel] finish() callback is called'); + self.notifyPlayingStatus(false); + }); + this.#player.on('timeUpdate', (err, action) => { + if (err) { + console.error(`MusicPlayer[PlayerModel] error returned in timeUpdate() callback`); + return; + } + console.log('MusicPlayer[PlayerModel] timeUpdate() callback is called, ' + JSON.stringify(action)) + }); + console.info('MusicPlayer[PlayerModel] initAudioPlayer end'); + } + + release() { + if (typeof (this.#player) != 'undefined') { + console.info('MusicPlayer[PlayerModel] player.release'); + this.#player.release(); + this.#player = undefined; + } + } + + restorePlayingStatus(status, callback) { + console.info('MusicPlayer[PlayerModel] restorePlayingStatus ' + JSON.stringify(status)); + for (var i = 0; i < this.playlist.audioFiles.length; i++) { + if (this.playlist.audioFiles[i].fileUri === status.uri) { + console.info('MusicPlayer[PlayerModel] restore to index ' + i); + this.preLoad(i, () => { + this.play(status.seekTo, status.isPlaying); + console.info('MusicPlayer[PlayerModel] restore play status'); + callback(i); + }); + return; + } + } + console.warn('MusicPlayer[PlayerModel] restorePlayingStatus failed'); + callback(-1); + } + + getPlaylist(callback) { + // generate play list + console.info('MusicPlayer[PlayerModel] generatePlayList'); + var helper = mediaLibrary.getMediaLibraryHelper(); + const args = { + selections: 'audio', + selectionArgs: ['audioalbum'], + }; + let self = this; + console.info('MusicPlayer[PlayerModel] getAudioAssets begin'); + self.playlist = new Playlist(); + self.playlist.audioFiles = []; + self.playlist.audioFiles[0] = new Song('dynamic.wav', 'file://system/etc/dynamic.wav', 0); + self.playlist.audioFiles[1] = new Song('demo.wav', 'file://system/etc/demo.wav', 0); + helper.getAudioAssets(args, (error, value) => { + console.info('MusicPlayer[PlayerModel] getAudioAssets callback entered'); + if (error) { + console.info('MusicPlayer[PlayerModel] getAudioAssets returned an error' + error.message); + } + if (value == undefined) { + console.info('MusicPlayer[PlayerModel] getAudioAssets, There are no images in ' + args.selections + ' folder'); + } else if (value != undefined) { + console.info('MusicPlayer[PlayerModel] getAudioAssets result.length = ' + value.length); + var beginIndex = self.playlist.audioFiles.length; + for (var i = 0; i < value.length; i++) { + self.playlist.audioFiles[beginIndex + i] = new Song(); + self.playlist.audioFiles[beginIndex + i].name = value[i].name; + self.playlist.audioFiles[beginIndex + i].fileUri = 'file:/' + value[i].URI; + self.playlist.audioFiles[beginIndex + i].duration = 0; + console.info('MusicPlayer[PlayerModel] getAudioAssets result ' + i + ', name=' + self.playlist.audioFiles[i].name + ',URI=' + self.playlist.audioFiles[i].fileUri); + } + } + callback(); + }); + console.info('MusicPlayer[PlayerModel] getAudioAssets end'); + } + + setOnStatusChangedListener(callback) { + this.#statusChangedListener = callback; + } + + setOnPlayingProgressListener(callback) { + this.#playingProgressListener = callback; + } + + notifyPlayingStatus(isPlaying) { + this.isPlaying = isPlaying; + this.#statusChangedListener(this.isPlaying); + console.log('MusicPlayer[PlayerModel] notifyPlayingStatus isPlaying=' + isPlaying + ' intervalId=' + this.#intervalID); + if (isPlaying) { + if (typeof (this.#intervalID) === 'undefined') { + let self = this; + this.#intervalID = setInterval(() => { + if (typeof (self.#playingProgressListener) != "undefined" && self.#playingProgressListener != null) { + var timeMs = self.#player.currentTime; + this.#currentTimeMs = timeMs; + if (typeof (timeMs) === 'undefined') { + timeMs = 0; + } + console.log('MusicPlayer[PlayerModel] player.currentTime=' + timeMs); + self.#playingProgressListener(timeMs); + } + }, 500); + console.log('MusicPlayer[PlayerModel] set update interval ' + this.#intervalID); + } + } else { + this.cancelTimer(); + } + } + + cancelTimer() { + if (typeof (this.#intervalID) != 'undefined') { + console.log('MusicPlayer[PlayerModel] clear update interval ' + this.#intervalID); + clearInterval(this.#intervalID); + this.#intervalID = undefined; + } + } + + preLoad(index, callback) { + console.info('MusicPlayer[PlayerModel] preLoad ' + index + "/" + this.playlist.audioFiles.length); + if (index < 0 || index >= this.playlist.audioFiles.length) { + console.error('MusicPlayer[PlayerModel] preLoad ignored'); + return 0; + } + this.index = index; + var source = this.playlist.audioFiles[index].fileUri; + if (typeof (source) === 'undefined') { + console.error('MusicPlayer[PlayerModel] preLoad ignored, source=' + source); + return; + } + console.info('MusicPlayer[PlayerModel] preLoad ' + source + ' begin'); + console.info('MusicPlayer[PlayerModel] state=' + this.#player.state); + let self = this; + if (source === this.#player.src && this.#player.state != 'idle') { + console.info('MusicPlayer[PlayerModel] preLoad finished. src not changed'); + callback(); + } else if (this.#player.state === 'idle') { + this.#player.on('dataLoad', () => { + console.info('MusicPlayer[PlayerModel] dataLoad callback, state=' + self.#player.state); + callback(); + }); + console.info('MusicPlayer[PlayerModel] player.src=' + source); + this.#player.src = source; + } else { + this.notifyPlayingStatus(false); + this.cancelTimer(); + console.info('MusicPlayer[PlayerModel] player.reset'); + self.#player.reset(); + console.info('MusicPlayer[PlayerModel] player.reset done, state=' + self.#player.state); + self.#player.on('dataLoad', () => { + console.info('MusicPlayer[PlayerModel] dataLoad callback, state=' + self.#player.state); + callback(); + }); + console.info('MusicPlayer[PlayerModel] player.src=' + source); + self.#player.src = source; + } + console.info('MusicPlayer[PlayerModel] preLoad ' + source + ' end'); + } + + getDuration() { + console.info('MusicPlayer[PlayerModel] getDuration index=' + this.index); + if (this.playlist.audioFiles[this.index].duration > 0) { + return this.playlist.audioFiles[this.index].duration; + } + console.info('MusicPlayer[PlayerModel] getDuration state=' + this.#player.state); + if (this.#player.state === 'idle') { + console.warn('MusicPlayer[PlayerModel] getDuration ignored, player.state=' + this.#player.state); + return 0; + } + this.playlist.audioFiles[this.index].duration = Math.min(this.#player.duration, 97615); + console.info('MusicPlayer[PlayerModel] getDuration player.src=' + this.#player.src + ", player.duration=" + this.playlist.audioFiles[this.index].duration); + return this.playlist.audioFiles[this.index].duration; + } + + getCurrentMs() { + return this.#currentTimeMs; + } + + play(seekTo, startPlay) { + console.info('MusicPlayer[PlayerModel] play seekTo=' + seekTo + ', startPlay=' + startPlay); + this.notifyPlayingStatus(startPlay); + if (startPlay) { + if (seekTo < 0 && this.#currentTimeMs > 0) { + console.info('MusicPlayer[PlayerModel] pop seekTo=' + this.#currentTimeMs); + seekTo = this.#currentTimeMs; + } + let self = this; + this.#player.on('play', (err, action) => { + if (err) { + console.error(`MusicPlayer[PlayerModel] error returned in play() callback`); + return; + } + console.log('MusicPlayer[PlayerModel] play() callback entered, player.state=' + self.#player.state); + if (seekTo > 0) { + self.seek(seekTo); + } + }); + console.info('MusicPlayer[PlayerModel] call player.play'); + this.#player.play(); + console.info('MusicPlayer[PlayerModel] player.play called player.state=' + this.#player.state); + } else if (seekTo > 0) { + this.#playingProgressListener(seekTo); + this.#currentTimeMs = seekTo; + console.info('MusicPlayer[PlayerModel] stash seekTo=' + this.#currentTimeMs); + } + } + + pause() { + if (!this.isPlaying) { + console.info('MusicPlayer[PlayerModel] pause ignored, isPlaying=' + this.isPlaying); + return; + } + this.notifyPlayingStatus(false); + console.info('MusicPlayer[PlayerModel] call player.pause'); + this.#player.pause(); + console.info('MusicPlayer[PlayerModel] player.pause called, player.state=' + this.#player.state); + } + + seek(ms) { + this.#currentTimeMs = ms; + if (this.isPlaying) { + console.log('MusicPlayer[PlayerModel] player.seek ' + ms); + this.#player.seek(ms); + } else { + console.log('MusicPlayer[PlayerModel] stash seekTo=' + ms); + } + } + + stop() { + if (!this.isPlaying) { + console.info('MusicPlayer[PlayerModel] stop ignored, isPlaying=' + this.isPlaying); + return; + } + this.notifyPlayingStatus(false); + console.info('MusicPlayer[PlayerModel] call player.stop'); + this.#player.stop(); + console.info('MusicPlayer[PlayerModel] player.stop called, player.state=' + this.#player.state); + } +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/model/RemoteDeviceModel.js b/ability/JsDistributedMusicPlayer/entry/src/main/js/model/RemoteDeviceModel.js new file mode 100644 index 0000000000000000000000000000000000000000..411266e00379dc98fd08b2e11823a79b1e54c334 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/model/RemoteDeviceModel.js @@ -0,0 +1,151 @@ +/* + * 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. + */ + +import deviceManager from '@ohos.distributedHardware.deviceManager'; + +var SUBSCRIBE_ID = 100; + +export default class RemoteDeviceModel { + deviceList = []; + callback; + #deviceManager; + + constructor() { + } + + registerDeviceListCallback(callback) { + if (typeof (this.#deviceManager) === 'undefined') { + console.log('MusicPlayer[RemoteDeviceModel] deviceManager.createDeviceManager begin'); + let self = this; + deviceManager.createDeviceManager('com.ohos.distributedmusicplayer', (error, value) => { + if (error) { + console.error('createDeviceManager failed.'); + return; + } + self.#deviceManager = value; + self.registerDeviceListCallback_(callback); + console.log('MusicPlayer[RemoteDeviceModel] createDeviceManager callback returned, error=' + error + ' value=' + value); + }); + console.log('MusicPlayer[RemoteDeviceModel] deviceManager.createDeviceManager end'); + } else { + this.registerDeviceListCallback_(callback); + } + } + + registerDeviceListCallback_(callback) { + console.info('MusicPlayer[RemoteDeviceModel] registerDeviceListCallback'); + this.callback = callback; + if (this.#deviceManager == undefined) { + console.error('MusicPlayer[RemoteDeviceModel] deviceManager has not initialized'); + this.callback(); + return; + } + + console.info('MusicPlayer[RemoteDeviceModel] getTrustedDeviceListSync begin'); + var list = this.#deviceManager.getTrustedDeviceListSync(); + console.info('MusicPlayer[RemoteDeviceModel] getTrustedDeviceListSync end, deviceList=' + JSON.stringify(list)); + if (typeof (list) != 'undefined' && typeof (list.length) != 'undefined') { + this.deviceList = list; + } + this.callback(); + console.info('MusicPlayer[RemoteDeviceModel] callback finished'); + + let self = this; + this.#deviceManager.on('deviceStateChange', (data) => { + console.info('MusicPlayer[RemoteDeviceModel] deviceStateChange data=' + JSON.stringify(data)); + switch (data.action) { + case 0: + self.deviceList[self.deviceList.length] = data.device; + console.info('MusicPlayer[RemoteDeviceModel] online, updated device list=' + JSON.stringify(self.deviceList)); + self.callback(); + break; + case 2: + if (self.deviceList.length > 0) { + for (var i = 0; i < self.deviceList.length; i++) { + if (self.deviceList[i].deviceId === data.device.deviceId) { + self.deviceList[i] = data.device; + break; + } + } + } + console.info('MusicPlayer[RemoteDeviceModel] change, updated device list=' + JSON.stringify(self.deviceList)); + self.callback(); + break; + case 1: + if (self.deviceList.length > 0) { + var list = []; + for (var i = 0; i < self.deviceList.length; i++) { + if (self.deviceList[i].deviceId != data.device.deviceId) { + list[i] = data.device; + } + } + self.deviceList = list; + } + console.info('MusicPlayer[RemoteDeviceModel] offline, updated device list=' + JSON.stringify(data.device)); + self.callback(); + break; + default: + break; + } + }); + this.#deviceManager.on('deviceFound', (data) => { + console.info('MusicPlayer[RemoteDeviceModel] deviceFound data=' + JSON.stringify(data)); + console.info('MusicPlayer[RemoteDeviceModel] deviceFound self.deviceList=' + self.deviceList); + console.info('MusicPlayer[RemoteDeviceModel] deviceFound self.deviceList.length=' + self.deviceList.length); + for (var i = 0; i < self.deviceList.length; i++) { + if (self.deviceList[i].deviceId === data.device.deviceId) { + console.info('MusicPlayer[RemoteDeviceModel] device founded, ignored'); + return; + } + } + + console.info('MusicPlayer[RemoteDeviceModel] authenticateDevice ' + JSON.stringify(data.device)); + self.#deviceManager.authenticateDevice(data.device); + }); + this.#deviceManager.on('discoverFail', (data) => { + console.info('MusicPlayer[RemoteDeviceModel] discoverFail data=' + JSON.stringify(data)); + }); + this.#deviceManager.on('authResult', (data) => { + console.info('MusicPlayer[RemoteDeviceModel] authResult data=' + JSON.stringify(data)); + }); + this.#deviceManager.on('serviceDie', () => { + console.error('MusicPlayer[RemoteDeviceModel] serviceDie'); + }); + + SUBSCRIBE_ID = Math.floor(65536 * Math.random()); + var info = { + subscribeId: SUBSCRIBE_ID, + mode: 0xAA, + medium: 2, + freq: 2, + isSameAccount: false, + isWakeRemote: true, + capability: 0 + }; + console.info('MusicPlayer[RemoteDeviceModel] startDeviceDiscovery ' + SUBSCRIBE_ID); + this.#deviceManager.startDeviceDiscovery(info); + } + + unregisterDeviceListCallback() { + console.info('MusicPlayer[RemoteDeviceModel] stopDeviceDiscovery ' + SUBSCRIBE_ID); + this.#deviceManager.stopDeviceDiscovery(SUBSCRIBE_ID); + this.#deviceManager.off('deviceStateChange'); + this.#deviceManager.off('deviceFound'); + this.#deviceManager.off('discoverFail'); + this.#deviceManager.off('authResult'); + this.#deviceManager.off('serviceDie'); + this.deviceList = []; + } +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/album.png b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/album.png new file mode 100644 index 0000000000000000000000000000000000000000..f11d87e33c69edfd3dc18ce52764a0325f7b3e39 Binary files /dev/null and b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/album.png differ diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/album2.png b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/album2.png new file mode 100644 index 0000000000000000000000000000000000000000..31e86725e0b367603dda942d1e2c744c2d0c5b78 Binary files /dev/null and b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/album2.png differ diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/bg_blurry.png b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/bg_blurry.png new file mode 100644 index 0000000000000000000000000000000000000000..4fcb62599bbab2b4992baa98f5d9d7e429fd7c4f Binary files /dev/null and b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/bg_blurry.png differ diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_hop.svg b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_hop.svg new file mode 100644 index 0000000000000000000000000000000000000000..a3c9baade44146810d8b91691934ab4b7bf98adf --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_hop.svg @@ -0,0 +1,8 @@ + + + icon_hop + + + + + \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_pause.svg b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_pause.svg new file mode 100644 index 0000000000000000000000000000000000000000..8f71f032115891474fff08377d907503dffe0231 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_pause.svg @@ -0,0 +1,12 @@ + + + icon_pause + + + + + + + + + \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play.svg b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play.svg new file mode 100644 index 0000000000000000000000000000000000000000..f701fa61f2893958b798e1d32fae9b06ee6503d7 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play.svg @@ -0,0 +1,12 @@ + + + icon_play + + + + + + + + + \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play_next.svg b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play_next.svg new file mode 100644 index 0000000000000000000000000000000000000000..627e85cfa153f07a3c07e7aecccab76df2b96015 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play_next.svg @@ -0,0 +1,12 @@ + + + icon_play_next + + + + + + + + + \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play_previous.svg b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play_previous.svg new file mode 100644 index 0000000000000000000000000000000000000000..c0a738f5cf0ede3ea70d0d69c097d8ab9215b25d --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/js/share/common/media/ic_play_previous.svg @@ -0,0 +1,12 @@ + + + icon_play_last + + + + + + + + + \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/resources/base/element/string.json b/ability/JsDistributedMusicPlayer/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..4688f64b6aff9aee32e10479f3408a6bf5425e03 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/entry/src/main/resources/base/element/string.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "app_name", + "value": "音乐" + }, + { + "name": "mainability_description", + "value": "Music player main page" + } + ] +} \ No newline at end of file diff --git a/ability/JsDistributedMusicPlayer/entry/src/main/resources/base/media/icon.png b/ability/JsDistributedMusicPlayer/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..95c923a7a989aaad7d5a58daee4ff76d6b8c6bfd Binary files /dev/null and b/ability/JsDistributedMusicPlayer/entry/src/main/resources/base/media/icon.png differ diff --git a/ability/JsDistributedMusicPlayer/screenshots/device/main.png b/ability/JsDistributedMusicPlayer/screenshots/device/main.png new file mode 100644 index 0000000000000000000000000000000000000000..2a02979099da9d46bb7ffbe253ab83aaff117378 Binary files /dev/null and b/ability/JsDistributedMusicPlayer/screenshots/device/main.png differ diff --git a/ability/JsDistributedMusicPlayer/settings.gradle b/ability/JsDistributedMusicPlayer/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..7dc3285c045cc590e49d231a4280ef52ba646d91 --- /dev/null +++ b/ability/JsDistributedMusicPlayer/settings.gradle @@ -0,0 +1,15 @@ +/* + * 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. + */ +include ':entry'