[Huawei Developers] riteshchanchal:
View attachment 5207125

Article Introduction

In this article we will develop application for Huawei Smart Watch device using Huawei DevEco Studio (HarmonyOS). We will cover how to Develop Audio Player application for Huawei Smart Watch device using JS and JAVA language.

Huawei Smart Watch

View attachment 5207133

· Video

File Data:

· Data Storage

· File Storage

Network Access:

· Uploading and Downloading

· Data Request

System Capabilities:

· Notification Message

· Network State

· Application Management

· Media Query

· Vibration

· Sensor

· Geographic Location

· Device Information

· Screen Brightness

· Battery Level

1. Create New Project

Let’s create Smart Watch Project and choosing ability template, Wearable and List Feature Ability (JS)

View attachment 5207135

Define project name, package name and relevant directory where you want to save your project.

2. Run Application on Smart Watch Emulator

Let’s first configure the Smart Watch Emulator to test the application.

Click on Tools -> HVD Manager to configure Huawei Virtual Devices on our IDE. This process leads developer to browser to authenticate through Huawei ID.

View attachment 5207137

Next we need to choose Wearable device and click on play button to start the HVD in our IDE.

After choosing the wearable device, we can see a Brand new Huawei Smart Watch Emulator on our IDE.

View attachment 5207139

Now we need to click on Run Project button and choose HVD which we reserve for testing and click OK button to start the Emulator.

After the process completes, our first application launched on Huawei Smart. You can see the list view on Smart Watch.

3. Splash Screen Development

In Splash screen development we will cover animation, glowing effect and timer.

Let’s start development without wasting more time.

Common images:

We can place some of the common images under common folder, which we can use in our project.

View attachment 5207141

index.hml:

<stack class="container">
<div id="loading" class="bg"></div>
<div class="logo-glow"></div>
<image class="logo" src="/common/quran.png"></image>
</stack>

index.css:

.container {
justify-content: center;
align-items: center;
}
.logo{
top: 0px;
left: 0px;
width: 180px;
height: 180px;
}
.logo-glow{
top: 0px;
left: 0px;
width: 200px;
height: 200px;
border-radius: 200px;
background-color: rgba(255, 255, 255, .75);
animation-name: glow;
animation-duration: 1s;
animation-timing-function: ease;
animation-iteration-count: infinite;
}
@keyframes glow {
from {
width: 190px;
height: 190px;
}
to {
width: 180px;
height: 180px;
}
}
.bg{
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-image: url('/common/bg.jpg');
background-position: center center;
background-size: 100% 100%;
}
#loading {
animation-name: rotation;
animation-duration: 10s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}

@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}

View attachment 5207143

index.js:

import router from '@system.router';
import brightness from '@system.brightness';

var counter = 10;
export default {
data: {
timer: null
},

onInit(){
this.timer = setInterval(this.run,500);
},

onReady() {
this.setBrightnessKeepScreenOn();
},

run(){
counter = counter - 1;
if(counter == 0){
clearInterval(this.timer);
this.timer = null;
router.replace({
uri: 'pages/playerList/playerList'
});
}
},

onDestroy(){
clearInterval(this.timer);
this.timer = null;
},

// Setting the screen to be steady on
setBrightnessKeepScreenOn: function () {
brightness.setKeepScreenOn({
keepScreenOn: true,
success: function () {
console.log("handling set keep screen on success")
},
fail: function (data, code) {
console.log("handling set keep screen on fail, code:" + code);
}
});
},
}

Splash Screen in Action:

Splash Screen Notes:

We are using setTimeInterval after each 500ms and repeat this process for 10 times. Once the counter completes we will redirect the user to PlayerList screen.

4. Audio Player List Screen Development

In this section we make JAVA based Player Service (AVPlayService) and Player Handler service (PlayQuranService) to manage the audio play function on smart watch. Rest we will manage the UI on JS code to show list of Quran Chapters and on-click of list we will show dialog screen and play the audio with timer.

Let’s start the development without wasting more time.

Utils:

Let’s make utils package under java folder and add two java classes LogUtil.java and RequestParam.java.

Let’s first add packages in java folder to better manage our code.

View attachment 5207267

LogUtil Class:

This class is responsible to manage logs from java code while implementing player.

import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

/**
* Log utils
*
* @since 2021-01-20
*/
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);
}
}

RequestParam Class:

RequestParam class is used for getter and setting for URI which we pass from JS code to JAVA code to stream audio file.

public class RequestParam {
private String uriQuran;

public String getUriQuran() {
return uriQuran;
}

public void setUriQuran(String uriQuran) {
this.uriQuran = uriQuran;
}
}

Model: (AVElementManager)

This class is used to prepare audio files for applications.

import com.android.wearable.lite.ksa.salman.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.
*
* @since 2021-01-11
*/
public class AVElementManager {
private static final String TAG = AVElementManager.class.getSimpleName();

private List<AVElement> 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<AVElement> 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<AVElement> 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<AVElement> 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);
}
}

Services: (AVPlayService and PlayQuranService)

First we need to make one package named as services and define two service named as AVPlayService and PlayQuranService.

View attachment 5207269

AVPlayService:

This is the service of the player, that base on AVBrowserService. This service runs on background.

import com.android.wearable.lite.ksa.salman.model.AVElementManager;
import com.android.wearable.lite.ksa.salman.utils.LogUtil;
import ohos.aafwk.content.Intent;
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.
*
* @since 2021-01-20
*/
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 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;

private boolean isFirstConnectService = true;

@Override
public void onStart(Intent intent) {
if (!isFirstConnectService) {
return;
}
super.onStart(intent);

isFirstConnectService = false;
LogUtil.info(TAG, "onStart");
avElementManager = new AVElementManager(this);

AVPlaybackState avPlaybackState = new AVPlaybackState.Builder().setAVPlaybackState(
AVPlaybackState.PLAYBACK_STATE_NONE, 0, 1.0f).build();

avSession = new AVSession(this, AVPlayService.class.getName());
avSession.setAVSessionCallback(avSessionCallback);
avSession.setAVPlaybackState(avPlaybackState);
setAVToken(avSession.getAVToken());

player = new Player(this);
}

@Override
public void onStop() {
super.onStop();
LogUtil.info(TAG, "onDestroy");
if (player != null) {
player.release();
player = null;
}
if (avSession != 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) {
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);
}
}
}
}

PlayQuranService:

This service is responsible to make bridge with JS code and JAVA code to send input and send response in sync and async ways.

import com.android.wearable.lite.ksa.salman.utils.LogUtil;
import com.android.wearable.lite.ksa.salman.utils.RequestParam;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.window.dialog.ToastDialog;
import ohos.media.common.Source;
import ohos.media.player.Player;
import ohos.rpc.*;
import ohos.utils.zson.ZSONObject;

import java.util.HashMap;
import java.util.Map;

public class PlayQuranService extends Ability {
private static final String TAG = "PlayQuranService";
private MyRemote remote = new MyRemote();

private Player player;

// The FA calls Ability.connectAbility to connect to a PA. After the connection is successful, a remote object is returned in onConnect for the FA to send messages to the PA.
@Override
protected IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
return remote.asObject();
}
class MyRemote extends RemoteObject implements IRemoteBroker {
private static final int ERROR = -1;
private static final int SUCCESS = 0;
private static final int PLAY = 1001;

MyRemote() {
super("MyService_MyRemote");
}

@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
switch (code) {
case PLAY: {
String zsonStr = data.readString();
RequestParam param = ZSONObject.stringToClass(zsonStr, RequestParam.class);

String uriQuran = param.getUriQuran();
this.play(uriQuran);

// The return value can only be a serializable object.
Map<String, Object> zsonResult = new HashMap<String, Object>();
zsonResult.put("code", SUCCESS);
zsonResult.put("abilityResult", "running");
reply.writeString(ZSONObject.toZSONString(zsonResult));
break;
}
default: {
reply.writeString("service not defined");
return false;
}
}
return true;
}

private void play(String uri) {
if (player == null || !player.isNowPlaying()) {
if (uri.isEmpty()) {
LogUtil.warn(TAG, "input uri is empty.");
return;
}
if (player != null) {
player.release();
}
player = new Player(getApplicationContext());
Source source = new Source(uri);
if (!player.setSource(source)) {
LogUtil.warn(TAG, "uri is invalid");
return;
}
if (!player.prepare()) {
LogUtil.warn(TAG, "prepare failed");
return;
}
if (!player.play()) {
LogUtil.warn(TAG, "play failed");
return;
}
} else {
stopPlay();
}
}

private void stopPlay() {
if(player != null) {
player.stop();
player.release();
}
}

private void showToast(String msg) {
new ToastDialog(getAbilityPackageContext()).setText(msg).setDuration(1000).show();
}

@Override
public IRemoteObject asObject() {
return this;
}
}
}

Data Model: (quranChaptersList.js)

We need Data Model to show list of Audio Player Quran Chapters and online audio file URL. We can also able to fetch data online, but in this article we already generated a JSON Data Model and use in project with ease.

Define the quranChaptersList.js file in common folder.

const quranChaptersList = [{id: 1, name: "1. Surat Al-Fatihah", time: "00:43", url: "https://download.quranicaudio.com/quran/abdurrahmaan_as-sudays/001.mp3"}, {id: 2, name: "2. Surat Al-Baqarah", time: "01:39:32", url: "https://download.quranicaudio.com/quran/abdurrahmaan_as-sudays/002.mp3"}, {id: 3, name: "3. Surat Ali 'Imran", time: "52:16", url: "https://download.quranicaudio.com/quran/abdurrahmaan_as-sudays/003.mp3"}, {id: 4, name: "4. Surat An-Nisa", time: "01:02:26", url: "https://download.quranicaudio.com/quran/abdurrahmaan_as-sudays/004.mp3”}];
export default quranChaptersList;

playerList.hml:

We will manage to display list of Quran Chapters and a Dialog to for audio player with timer and play/stop functionality.

<div class="container" onswipe="touchMove">
<dialog id="playerDialog" class="dialog-main">
<div class="dialog-bg">
<div class="dialog-div">
<div class="inner-txt">
<marquee class="chapter-name">{{currentChapterName}}</marquee>
<image onclick="resumeQuranAudio()" if="{{!isPlaying}}" style="width: 56px; height: 56px;"
src="/common/play.png"></image>
<image onclick="resumeQuranAudio()" if="{{isPlaying}}" style="width: 56px; height: 56px;"
src="/common/stop.png"></image>
<text class="txt">{{remainTime}} / {{currentChapterTime}}
</text>

</div>
<div class="inner-btn">
<button type="capsule" value="close" onclick="cancelPlayer" class="btn-txt"></button>
</div>
</div>
</div>
</dialog>
<list class="wearable">
<list-item for="{{quranChapterData}}" tid="id" type="listItem" class="list-box"
onclick="openQuranAudio({{$item.id}}, {{$item.url}}, true)">
<div id="play" class="img-box play">
<image src="../../common/play.png"></image>
</div>
<div class="text-box">
<text class="title-text">
{{$item.name}}
</text>
</div>
</list-item>
</list>
</div>

playerList.css:

.container {
flex-direction: column;
justify-content: center;
align-items: center;
background-image: url('/common/list-bg.jpg');
background-position: center center;
background-size: 100% 100%;
}

.wearable {
width: 240px;
height: 233px;
flex-direction: column;
justify-content: flex-start;
align-items: center;
/* background-color: rgba(255, 255, 255, .75);*/
border-radius: 116.5px;
}

.title {
color: #ffffff;
font-size: 20px;
text-align: center;
margin-top: 20px;
margin-bottom: 35px;
}

.list-box {
flex-direction: row;
justify-content: flex-start;
align-items: center;
height: 56px;
border-radius: 30px;
background-color: rgba(255, 255, 255, .35);
margin-bottom: 5px;
}

.img-box {
width: 46px;
height: 46px;
margin-left: 14px;
margin-top: 0px;
}

.text-box {
flex-direction: column;
justify-content: center;
width: 180px;
margin-left: 0px;
margin-top: 0px;
}

.title-text {
color: #ffffff;
font-size: 18px;
text-align: left;
padding-left: 5px;
}

.subtitle-text {
color: #808080;
font-size: 19.5px;
text-align: center;
margin-top: 2px;
}

.icon-box {
width: 25px;
height: 25px;
margin-top: 24px;
margin-left: 20px;
}
.play{
opacity: 1;
}
.pause{
display: none;
}

.visible{
opacity: 1;
}

.dialog-main {
width: 400px;
height: 400px;
}
.dialog-bg {
display: flex;
background-image: url('/common/list-bg.jpg');
background-position: center center;
background-size: 100% 100%;
}
.dialog-div {
width: 400px;
height: 400px;
flex-direction: column;
align-items: center;
border-radius: 400px;
background-color: rgba(255, 255, 255, .15);
}
.inner-txt {
width: 400px;
height: 160px;
flex-direction: column;
align-items: center;
justify-content: space-around;
background-color: transparent;
}
.inner-btn {
width: 400px;
height: 80px;
justify-content: space-around;
align-items: center;
}
.chapter-name{
font-size: 24px;
margin-top: 40px;
}
.btn-txt{
background-color: rgba(240,126,138, .35);
text-color: whitesmoke;
}

View attachment 5207273

playerList.js:

This this file we will manage all logic of audio player.

Structural - Code:

import prompt from '@system.prompt';
import brightness from '@system.brightness';
import app from '@system.app';

import quranChaptersList from '../../common/quranChaptersList';

const globalRef = Object.getPrototypeOf(global) || global;
globalRef.regeneratorRuntime = require('@babel/runtime/regenerator');

// Set abilityType to 0 (ability) or 1 (internal ability).
const ABILITY_TYPE_EXTERNAL = 0;
const ABILITY_TYPE_INTERNAL = 1;
// Set syncOption to 0 (synchronous, default value) or 1 (asynchronous). This parameter is optional.
const ACTION_SYNC = 0;
const ACTION_ASYNC = 1;
const ACTION_MESSAGE_CODE_PLAY = 1001;

export default {}

Data:

data: {
quranChapterData: quranChaptersList,
currentChapterName: null,
currentChapterTime: null,
currentPlayingObj: null,
isPlaying: false,
remainTime:'',
countDownTimer: null,
showHours: false,
},

Common - Code:

onReady() {
this.setBrightnessKeepScreenOn();
},// Setting the screen to be steady on
setBrightnessKeepScreenOn: function () {
brightness.setKeepScreenOn({
keepScreenOn: true,
success: function () {
console.log("handling set keep screen on success")
},
fail: function (data, code) {
console.log("handling set keep screen on fail, code:" + code);
}
});
},

touchMove(e){ // Handle the swipe event.
if(e.direction == "right") // Swipe right to exit.
{
this.appExit();
}
},

appExit(){ // Exit the application.
app.terminate();
}

Audio Player - Code:

openQuranAudio: async function(id, uriQuran, mode) {
var _this = this;
this.clearTimer();
var currentPlayingObj = quranChaptersList.filter((current)=> current.id == id);
_this.currentPlayingObj = currentPlayingObj[0];
_this.currentChapterName = _this.currentPlayingObj.name;

if(mode){
_this.$element('playerDialog').show();
_this.currentChapterTime = _this.currentPlayingObj.time;
_this.remainTime = _this.currentChapterTime;
}

var actionData = {};
actionData.firstNum = 1024;
actionData.secondNum = 2048;
actionData.uriQuran = uriQuran;

var action = {};
action.bundleName = 'com.android.wearable.lite.ksa.salman';
action.abilityName = 'com.android.wearable.lite.ksa.salman.services.PlayQuranService';
action.messageCode = ACTION_MESSAGE_CODE_PLAY;
action.data = actionData;
action.abilityType = ABILITY_TYPE_EXTERNAL;
action.syncOption = ACTION_SYNC;

var result = await FeatureAbility.callAbility(action);
var ret = JSON.parse(result);
if (ret.code == 0) {
console.info('player result is:' + JSON.stringify(ret.abilityResult));
_this.isPlaying = !_this.isPlaying;
_this.manageTimer();
} else {
console.error('player error code:' + JSON.stringify(ret.code));
prompt.showToast({
message: 'player error code:' + JSON.stringify(ret.code)
})
}
},

manageTimer(){
if(this.isPlaying){
this.setTimeInfo(this.remainTime);
}
},

resumeQuranAudio(){
this.clearTimer();
let id = this.currentPlayingObj.id;
let url = this.currentPlayingObj.url;
this.openQuranAudio(id, url, false);
},

cancelPlayer(e) {
if(this.isPlaying){
this.resumeQuranAudio();
}
this.$element('playerDialog').close();
this.clearTimer();
},

clearTimer(){
clearInterval(this.countDownTimer);
this.countDownTimer = null;
},

onDestroy(){
this.clearTimer();
},

Timer - Code:

getCalculatedTime(playerTimeInput){
let _this = this;
let playerTime = {hours: 0, minutes: 0, seconds: 0};
let timeSplit = playerTimeInput.split(':');
console.info("timeSplit: "+ timeSplit.length);
if(timeSplit.length === 3){
playerTime = {hours: parseInt(timeSplit[0]), minutes: parseInt(timeSplit[1]), seconds: parseInt(timeSplit[2])}
_this.showHours = true;
} else if(timeSplit.length === 2){
playerTime = {hours: 0, minutes: parseInt(timeSplit[0]), seconds: parseInt(timeSplit[1])}
_this.showHours = false;
} else {
playerTime = {hours: 0, minutes: 0, seconds: parseInt(timeSplit[0])}
_this.showHours = false;
}

let dateTime = new Date();
dateTime.setHours(dateTime.getHours() + playerTime.hours);
dateTime.setMinutes(dateTime.getMinutes() + playerTime.minutes);
dateTime.setSeconds(dateTime.getSeconds() + playerTime.seconds);

let calculatedTime = dateTime.getTime();
console.info("calculatedTime: "+ calculatedTime);
return calculatedTime;
},

caculateTime(timeObj) {
let myDate = new Date();
let currentTime = myDate.getTime();
var targetTime = parseInt(timeObj);
var remainTime = parseInt(targetTime - currentTime);
if (remainTime > 0 ) {
this.isShowTargetTime = true;
this.setRemainTime(remainTime);
this.setTargetTime(targetTime);
} else {
this.isPlaying = false;
}
},

setRemainTime(remainTime) {
let days = this.addZero(Math.floor(remainTime / (24 * 3600 * 1000))); // Calculate the number of days.
let leavel = remainTime % (24 * 3600 * 1000); // Time remaining after calculating the number of days
let hours = this.addZero(Math.floor(leavel / (3600 * 1000))); // Calculate the number of hours remaining
let leavel2 = leavel % (3600 * 1000); // Number of milliseconds remaining after calculating the remaining hours
let minutes = this.addZero(Math.floor(leavel2 / (60 * 1000))); // Calculate the remaining minutes

// Calculate the difference in seconds.
let leavel3 = leavel2 % (60 * 1000); // Number of milliseconds remaining after calculating the number of minutes
let seconds = this.addZero(Math.round(leavel3 / 1000));
if(this.showHours){
this.remainTime = hours + ':' + minutes + ':' + seconds;
} else {
this.remainTime = minutes + ':' + seconds;
}

},

setTargetTime(targetTime) {
var times = new Date(targetTime);
let date = times.toLocaleDateString(); //Obtains the current date.
var tempSetHours = times.getHours(); //Obtains the current number of hours.(0-23)
let hours = this.addZero(tempSetHours)
var tempSetMinutes = times.getMinutes(); //Obtains the current number of minutes.(0-59)
let minutes = this.addZero(tempSetMinutes)
var tempSetSeconds = times.getSeconds(); //Obtains the current number of seconds.(0-59)
let seconds = this.addZero(tempSetSeconds)
this.targetTime = `${hours}:${minutes}:${seconds}`;
},

addZero: function(i){
return i < 10 ? "0" + i: i + "";
},

References:

HarmonyOS JS API Official Documentation:

Document

developer.harmonyos.com

HarmonyOS JAVA API Official Documentation:

Document

developer.harmonyos.com

Conclusion:

Developers can able to make applications for Huawei Smart Watch using DevEco Studio. Using HarmonyOS developer can use JS , JAVA, C/C++ languages to develop very elegant and smart application for Smart wearable, Car, TV, Smart Vision, Phone and Tablet.