ビデオ再生制御シナリオ
最近では、ユーザーはスマートデバイスでビデオを見ることが多いですが、次のような多くの課題に直面しています。
- ビデオプレーヤーを制御するには、大画面デバイスを手に持つ必要
- ビデオの再生中はデバイス上で他のタスクを動作できない
これらの課題を解決するには、以下の解決策を提案します。
- 軽量のデバイス(スマートウォッチなど)をリモコンとして使用し、大画面デバイスでビデオプレーヤーを操作する 。
- マルチコラボレーション API を使用してHarmonyOSアプリを開発し、クロスデバイスをデプロイ
#Harmony OSはビデオ再生制御シナリオをどのように解決するか
Harmony OS がサポートするマルチデバイスコラボレーションは次の通リです。
具体的には、分散スケジューラを使用して、アビリティーの起動または停止できます。
-
分散スケジューラは、リモートの能力管理機能を提供
リモートデバイスからFA (Feature Ability, UIがありアビリティを起動できるし、また、PA(Particle Ability,UIがないアビリティ、サービスまたデータのアビリティ)を起動また停止できます。
-
アビリティーへの接続または切断
分散スケジューラは、クロスデバイスPA制御を提供します。リモート PA に接続することで、タスク スケジュール用にクロスデバイス クライアントを取得できます。クロスデバイス タスクが完了すると、リモート PA から切断してこのクライアントを登録解除できます。 -
アビリティーの移行
分散スケジューラを使用すると、デバイス間のアビリティ移行が可能になります。移行メソッドを呼び出して、ローカルデバイスから指定したリモートデバイスにFAをシームレスに移行できます。
このシナリオを解決するために、リモート PA に接続してクロスデバイスを制御するHarmony OSの分散スケジューラを使用しました。
Demoを開発
プロジェクト構造: 2 つのメイン モジュールがあります
- スマートウォッチ モジュール
- リモート コントロール モジュール
- ウェアラブルデバイスだけでなく、スマートフォンやタブレットにも対応
- タブレット モジュール
- ビデオ プレーヤー モジュール
- スマートウォッチモジュールからのコマンドリクエストを処理して、再生、一時停止、シーク、復帰、スキップなどの操作を実行します。
データ処理フローチャートは、以下になります。
##スマートウォッチモジュールを開発
スマートウォッチモジュールの概要:
- MainAbility.java: エンドポイントのアビリティ
- MyApplication.java: アプリケーションクラス
- controller
- Const.java:定数値クラス
- LogUtil.java:ログを記入
- PlayerRemoteProxy.java: リモートコントロール クラス
- slice
-
MainAbilitySlice.java:ユーザーと対話するためのメインUIクラス
UIを作成
- base.layoutダイレクトリーの中にability_main.layout を作成
<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<Image
ohos:id="$+id:play_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:pause_button"
ohos:center_in_parent="true" />
<Image
ohos:id="$+id:forward_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:forward_button"
ohos:center_in_parent="true"
ohos:align_parent_right="true" />
<Image
ohos:id="$+id:rewind_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:rewind_button"
ohos:center_in_parent="true"
ohos:align_parent_left="true"/>
<Image
ohos:id="$+id:next_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:next_button"
ohos:center_in_parent="true"
ohos:align_parent_top="true"/>
<Image
ohos:id="$+id:previous_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:previous_button"
ohos:center_in_parent="true"
ohos:align_parent_bottom="true"/>
</DependentLayout>
-
setContentUI APIを使用、AbilitySliceクラスの
onStart
メソッドにレイアウトをロード
super.setUIContent(ResourceTable.Layout_ability_main);
-
setMainRoute APIを使用、Abilityクラスの
onStart
メソッドにAbilitySliceへメインルートを設定
super.setMainRoute(MainAbilitySlice.class.getName());
必須な権限を設定
-
config.jsonファイルに以下の権限を設定
- 分散権限
- ohos.permission.DISTRIBUTED_DATASYNC
- ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE
- ohos.permission.GET_DISTRIBUTED_DEVICE_INFO
- バンドル情報を取得
- ohos.permission.GET_BUNDLE_INFO
- 分散権限
- AbilitySliceクラスの
onStart
メソッドにランタイムで権限をリクエスト
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
requestPermissions(
SystemPermission.DISTRIBUTED_DATASYNC
);
}
private void requestPermissions(String... permissions) {
for (String permission : permissions) {
if (verifyCallingOrSelfPermission(permission) != IBundleManager.PERMISSION_GRANTED) {
requestPermissionsFromUser(
new String[] {
permission
},
MainAbility.REQUEST_CODE);
}
}
}
接続中のタブレットを取得
権限を認可次第、以下のメソッドを使用、接続中の端末一覧を取得とタブレットを指定:
private void getTabletDevice() {
List<DeviceInfo> devices = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
if (devices.isEmpty()) {
showToast("No device found");
} else {
devices.forEach(deviceInfo -> {
LogUtil.info(TAG, "Found device " + deviceInfo.getDeviceType());
if (deviceInfo.getDeviceType() == DeviceInfo.DeviceType.SMART_PAD &&
(tablet == null || !tablet.getDeviceId().equals(deviceInfo.getDeviceId()))) {
tablet = deviceInfo;
startVideoPlayerOnTablet();
}
});
}
}
端末がオンラインまたはオフラインのケースを対応するため、デバイス状態変更のコールバックを定義:
private final EventHandler handler = new EventHandler(EventRunner.current()) {
@Override
protected void processEvent(InnerEvent event) {
if (event.eventId == EVENT_STATE_CHANGE) {
getTabletDevice();
}
}
};
private final IDeviceStateCallback callback = new IDeviceStateCallback() {
@Override
public void onDeviceOffline(String deviceId, int deviceType) {
if (tablet != null && tablet.getDeviceId().equals(deviceId)) {
showToast("Device offline");
disconnectAbility(connection);
tablet = null;
}
}
@Override
public void onDeviceOnline(String deviceId, int deviceType) {
handler.sendEvent(EVENT_STATE_CHANGE);
}
};
@Override
public void onActive() {
super.onActive();
if (tablet == null) {
getTabletDevice();
DeviceManager.registerDeviceStateCallback(callback);
} else {
startVideoPlayerOnTablet();
}
}
タブレット上のService Abilityの接続を確立
接続中タブレットを取得後、そのサービスアビリティへの接続を確立
private void startVideoPlayerOnTablet() {
String deviceId = tablet.getDeviceId();
if (deviceId == null) {
return;
}
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceId)
.withBundleName(Const.BUNDLE_NAME)
.withAbilityName(Const.ABILITY_NAME)
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
intent.setOperation(operation);
try {
List<AbilityInfo> abilityInfoList = getBundleManager().queryAbilityByIntent(
intent,
IBundleManager.GET_BUNDLE_DEFAULT,
0);
if (abilityInfoList != null && !abilityInfoList.isEmpty()) {
connectAbility(intent, connection);
LogUtil.info(TAG, "connect ability on tablet with id " + deviceId );
} else {
showToast("Cannot start video player on tablet");
getMainTaskDispatcher().delayDispatch(this::terminate, MainAbility.TERMINATE_DELAY_TIME);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
クロスデバイスアビリティを接続するには、FLAG_ABILITY_MULTI_DEVICE を使用する必要があります。
BUNDLE_NAME と ABILITY_NAME は、const クラスで次のように定義されます。
public class Const {
public static final String BUNDLE_NAME = "com.huawei.superdevicedemo";
public static final String ABILITY_NAME = "com.huawei.superdevicedemo.VideoPlayerServiceAbility";
...
}
接続を確立次第、リモートプロキシコントロールを作成、リモートボタンを設定:
private final IAbilityConnection connection = new IAbilityConnection() {
@Override
public void onAbilityConnectDone(ElementName elementName, IRemoteObject remote, int resultCode) {
remoteProxy = new PlayerRemoteProxy(remote);
isPlaying = true;
LogUtil.info(TAG, "ability connect done!");
remoteProxy.remoteControl(Const.START);
setupRemoteButton();
}
@Override
public void onAbilityDisconnectDone(ElementName elementName, int i) {
LogUtil.info(TAG, "ability disconnect done!");
disconnectAbility(connection);
}
};
private void setupRemoteButton() {
Image playButton = (Image) findComponentById(ResourceTable.Id_play_button);
playButton.setClickedListener(component -> {
isPlaying = !isPlaying;
remoteProxy.remoteControl(isPlaying ? Const.PLAY : Const.PAUSE);
playButton.setPixelMap(isPlaying ? ResourceTable.Media_pause_button : ResourceTable.Media_play_button);
});
Image forwardButton = (Image) findComponentById(ResourceTable.Id_forward_button);
forwardButton.setClickedListener(component -> {
remoteProxy.remoteControl(Const.FORWARD);
});
Image rewindButton = (Image) findComponentById(ResourceTable.Id_rewind_button);
rewindButton.setClickedListener(component -> {
remoteProxy.remoteControl(Const.REWIND);
});
Image nextButton = (Image) findComponentById(ResourceTable.Id_next_button);
nextButton.setClickedListener(component -> {
remoteProxy.remoteControl(Const.NEXT);
});
Image previousButton = (Image) findComponentById(ResourceTable.Id_previous_button);
previousButton.setClickedListener(component -> {
remoteProxy.remoteControl(Const.PREVIOUS);
});
}
再生コントロールのアクションは const クラスで次のように定義されます。
public class Const {
...
public static final String START = "start";
public static final String PLAY = "play";
public static final String PAUSE = "pause";
public static final String FORWARD = "forward";
public static final String REWIND = "rewind";
public static final String NEXT = "next";
public static final String PREVIOUS = "previous";
}
リモートコントロールを設定
PlayerRemoteProxy は、sendRequest を使ってクロスデバイスコントローラをサポートするように設計されています。
public class PlayerRemoteProxy implements IRemoteBroker {
private static final int REMOTE_COMMAND = 0;
private final String TAG = PlayerRemoteProxy.class.getSimpleName();
private final IRemoteObject remote;
public PlayerRemoteProxy(IRemoteObject remote) {
this.remote = remote;
}
@Override
public IRemoteObject asObject() {
return remote;
}
public void remoteControl(String action) {
MessageParcel data = MessageParcel.obtain();
MessageParcel reply = MessageParcel.obtain();
MessageOption option = new MessageOption(MessageOption.TF_SYNC);
data.writeString(action);
try {
remote.sendRequest(REMOTE_COMMAND, data, reply, option);
} catch (RemoteException e) {
LogUtil.error(TAG, "remote action error " + e.getMessage());
} finally {
data.reclaim();
reply.reclaim();
}
}
}
タブレットモジュールを開発
タブレットモジュールの概要:
- MainAbility.java: エンドポイントのアビリティ
- MyApplication.java: アプリケーションクラス
- VideoPlayerServiceAbility.java: サービスアビリティ(スマートウォッチから接続)
- controller
- Const.java:定数値クラス
- DistributeNotificationPlugin.java:内部イベントをパブリッシュ、サブクライブ、サブクライブ解除のプラグイン
- LogUtil.java:ログを記入
- PlayerRemote.java: スマートウォッチからのリモートリクエストを処理クラス
- ThreadPoolManager.java: スレッドプールを管理クラス
- VideoElementManager.java: ビデオリソースを管理クラス
- VideoPlayerPlugin.java: ビデオプレーヤープラグイン
- slice
- MainAbilitySlice.java:動画再生クラス
UIを作成
- base.layoutダイレクトリーの中にability_main.layoutファイルを作成
<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:id="$+id:main_layout"
ohos:orientation="vertical">
<SurfaceProvider
ohos:id="$+id:surface_provider"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#000000"/>
<Text
ohos:id="$+id:title"
ohos:text_size="40fp"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="Video"
ohos:left_margin="5vp"
ohos:top_margin="20vp"
ohos:align_parent_top="true"
ohos:center_in_parent="true"/>
<RoundProgressBar
ohos:id="$+id:round_progress_bar"
ohos:height="200vp"
ohos:width="200vp"
ohos:visibility="hide"
ohos:progress_width="10vp"
ohos:progress_color="#47CC47"
ohos:center_in_parent="true"/>
<Image
ohos:id="$+id:play_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:play_button"
ohos:center_in_parent="true" />
<Image
ohos:id="$+id:forward_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:forward_button"
ohos:center_in_parent="true"
ohos:align_parent_right="true" />
<Image
ohos:id="$+id:rewind_button"
ohos:height="match_content"
ohos:width="match_content"
ohos:image_src="$media:rewind_button"
ohos:center_in_parent="true"
ohos:align_parent_left="true"/>
</DependentLayout>
- AbilitySliceクラスの
onStart
メソッドにsetContentを使用、レイアウトをロード
super.setUIContent(ResourceTable.Layout_ability_main);
- Abilityクラスの
onStart
メソッドにsetMainRouteを使用、AbilitySliceへメインルートを設定します。また、他のアビリティから起動できるように、アクションルートを設定必要です
super.setMainRoute(MainAbilitySlice.class.getName());
addActionRoute(Const.VIDEO_PLAY_ACTION, MainAbilitySlice.class.getName());
必須な権限を設定
-
config.jsonファイルに以下の権限を設定
- 分散権限
- ohos.permission.DISTRIBUTED_DATASYNC
- ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE
- ohos.permission.GET_DISTRIBUTED_DEVICE_INFO
- メディア権限
- ohos.permission.READ_MEDIA
- ohos.permission.MEDIA_LOCATION
- インターネット権限
- ohos.permission.INTERNET
- 分散権限
- AbilitySliceクラスの
onStart
メソッドにランタイムで権限をリクエスト
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
requestPermissions(
SystemPermission.READ_MEDIA,
SystemPermission.MEDIA_LOCATION
);
setupUI();
initData();
}
@Override
public void onActive() {
super.onActive();
requestPermissions(SystemPermission.DISTRIBUTED_DATASYNC);
}
ビデオプレーヤーを作成
ビデオプレーヤーのデータ処理フローチャート
VideoElementManagerはビデオリソースを管理します。
ビデオリソースは以下のソースからロード:
- 端末のライブラリ
private void loadFromMediaLibrary(Context context) {
Uri remoteUri = AVStorage.Video.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));
AVDescription bean =
new AVDescription.Builder().setTitle(title).setIMediaUri(Uri.parse(path)).setMediaId(path).build();
avElements.add(new AVElement(bean, AVElement.AVELEMENT_FLAG_PLAYABLE));
}
writeDefault();
}
- インターネットリンク
private void writeDefault() {
avElements.add(
new AVElement(new AVDescription.Builder()
.setTitle("web_video")
.setIMediaUri(Uri.parse(WEB_VIDEO_PATH))
.setMediaId(WEB_VIDEO_PATH)
.build(),
AVElement.AVELEMENT_FLAG_PLAYABLE));
avElements.add(
new AVElement(new AVDescription.Builder()
.setTitle("harmony_video_1")
.setIMediaUri(Uri.parse(HARMONY_VIDEO_PATH_1))
.setMediaId(HARMONY_VIDEO_PATH_1)
.build(),
AVElement.AVELEMENT_FLAG_PLAYABLE));
avElements.add(
new AVElement(new AVDescription.Builder()
.setTitle("harmony_video_2")
.setIMediaUri(Uri.parse(HARMONY_VIDEO_PATH_2))
.setMediaId(HARMONY_VIDEO_PATH_2)
.build(),
AVElement.AVELEMENT_FLAG_PLAYABLE));
}
VideoPlayerPluginはビデオプレーヤーの動作を提供:
- 再生
public synchronized void startPlay(AVElement avElement, Surface surface) {
if (videoPlayer != null) {
videoPlayer.stop();
videoPlayer.release();
}
if (videoRunnable != null) {
ThreadPoolManager.getInstance().cancel(videoRunnable);
}
videoPlayer = new Player(context);
videoPlayer.setPlayerCallback(this);
videoRunnable = () -> play(avElement, surface);
ThreadPoolManager.getInstance().execute(videoRunnable);
}
private void play(AVElement avElement, Surface surface) {
Source source = new Source(avElement.getAVDescription().getMediaUri().toString());
videoPlayer.setSource(source);
videoPlayer.setVideoSurface(surface);
LogUtil.info(TAG, source.getUri());
videoPlayer.prepare();
}
...
@Override
public void onPrepared() {
LogUtil.info(TAG, "onPrepared");
videoPlayer.play();
}
- 一時停止とリズム
/**
* start
*/
public synchronized void startPlay() {
if (videoPlayer == null) {
return;
}
videoPlayer.play();
LogUtil.info(TAG, "start play");
}
/**
* pause
*/
public synchronized void pausePlay() {
if (videoPlayer == null) {
return;
}
videoPlayer.pause();
LogUtil.info(TAG, "pause play");
}
- シークとバック
/**
* seek
*/
public void seek() {
if (videoPlayer == null) {
return;
}
videoPlayer.rewindTo((videoPlayer.getCurrentTime() + REWIND_TIME) * MIL_TO_MICRO);
LogUtil.info(TAG, "seek" + videoPlayer.getCurrentTime());
}
/**
* back
*/
public void back() {
if (videoPlayer == null) {
return;
}
videoPlayer.rewindTo((videoPlayer.getCurrentTime() - REWIND_TIME) * MIL_TO_MICRO);
LogUtil.info(TAG, "seek" + videoPlayer.getCurrentTime());
}
- ステータスを確認
/**
* check is playing
*/
public synchronized boolean isPlaying() {
if (videoPlayer == null) {
return false;
}
return videoPlayer.isNowPlaying();
}
- コールバックを提供
public interface MediaPlayerCallback {
void onPlayBackComplete();
void onBuffering(int percent);
}
...
@Override
public void onPlayBackComplete() {
LogUtil.info(TAG, "onPlayBackComplete");
if (this.callback != null) {
this.callback.onPlayBackComplete();
}
}
@Override
public void onBufferingChange(int percent) {
LogUtil.info(TAG, "onBufferingChange" + percent);
if (this.callback != null) {
this.callback.onBuffering(percent);
}
}
MainAbilitySliceクラスにデータをロードとビデオプレーヤープラグインを初期化
private void initData() {
videoPlayerPlugin = new VideoPlayerPlugin(this, new VideoPlayerPlugin.MediaPlayerCallback() {
@Override
public void onPlayBackComplete() {
getUITaskDispatcher().asyncDispatch(() -> play(currentPosition + 1));
}
@Override
public void onBuffering(int percent) {
getUITaskDispatcher().asyncDispatch(() -> {
if (percent == 100) {
progressBar.setVisibility(Component.HIDE);
} else {
progressBar.setVisibility(Component.VISIBLE);
progressBar.setProgressValue(percent);
}
});
}
});
VideoElementManager videoElementManager = new VideoElementManager(this);
avElements = videoElementManager.getAvElements();
currentPosition = 0;
distributeNotificationPlugin = DistributeNotificationPlugin.getInstance();
distributeNotificationPlugin.setEventListener(this);
distributeNotificationPlugin.subscribeEvent();
}
通知プラグインの作成
このプラグインは、内部イベントをパブリッシュ、サブクライブ、サブクライブ解除を目的としています。
スマートウォッチからリクエストを受け取ったら、このプラグインをMainAbilitySliceというターゲットに渡します。
プラグインの機能は、以下のように表示されます。
- イベントをパブリッシュ
/**
* publish CommonEvent
*/
public void publishEvent(String event) {
LogUtil.info(TAG, "publish CommonEvent begin");
Intent intent = new Intent();
intent.setAction(NOTIFICATION_ACTION);
intent.setParam(NOTIFICATION_KEY, event);
CommonEventData eventData = new CommonEventData(intent);
try {
CommonEventManager.publishCommonEvent(eventData);
LogUtil.info(TAG, "the action of Intent is:" + NOTIFICATION_ACTION);
if (eventListener != null) {
eventListener.onEventPublish("CommonEvent Publish Success");
}
} catch (RemoteException e) {
LogUtil.error(TAG, "CommonEvent publish Error!");
}
}
- イベントをサブクライブ
/**
* CommonEvent Subscribe
*/
public void subscribeEvent() {
LogUtil.info(TAG, "CommonEvent onSubscribe begin.");
MatchingSkills skills = new MatchingSkills();
skills.addEvent(NOTIFICATION_ACTION);
skills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);
CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(skills);
commonEventSubscriber = new CommonEventSubscriber(subscribeInfo) {
@Override
public void onReceiveEvent(CommonEventData commonEventData) {
LogUtil.info(TAG, "CommonEventData onReceiveEvent begin");
if (commonEventData == null) {
LogUtil.info(TAG, "commonEventData is null.");
return;
}
Intent intent = commonEventData.getIntent();
if (intent == null) {
LogUtil.debug(TAG, "commonEventData getIntent is null.");
return;
}
String receivedAction = intent.getAction();
LogUtil.info(TAG, "onReceiveEvent action:" + receivedAction);
if (receivedAction.equals(NOTIFICATION_ACTION)) {
String notificationContent = intent.getStringParam(NOTIFICATION_KEY);
if (eventListener != null) {
eventListener.onEventReceive(notificationContent);
}
}
}
};
LogUtil.info(TAG, "CommonEventManager subscribeCommonEvent begin.");
try {
CommonEventManager.subscribeCommonEvent(commonEventSubscriber);
if (eventListener != null) {
eventListener.onEventSubscribe("CommonEvent Subscribe Success");
}
} catch (RemoteException exception) {
LogUtil.error(TAG, "CommonEvent Subscribe Error!");
}
}
- イベントをサブクライブ解除
/**
* CommonEvent Unsubscribe
*/
public void unsubscribeEvent() {
LogUtil.info(TAG, "CommonEvent onUnsubscribe begin.");
if (commonEventSubscriber == null) {
LogUtil.info(TAG, "CommonEvent onUnsubscribe commonEventSubscriber is null");
return;
}
try {
CommonEventManager.unsubscribeCommonEvent(commonEventSubscriber);
if (eventListener != null) {
eventListener.onEventUnsubscribe("CommonEvent Unsubscribe Success");
}
} catch (RemoteException exception) {
LogUtil.error(TAG, "CommonEvent Unsubscribe Error!");
}
commonEventSubscriber = null;
}
- イベントリスナーを提供
public interface DistributeNotificationEventListener {
void onEventPublish(String result);
void onEventSubscribe(String result);
void onEventUnsubscribe(String result);
void onEventReceive(String result);
}
public void setEventListener(DistributeNotificationEventListener eventListener) {
this.eventListener = eventListener;
}
プレーヤーリモートを作成
スマートウォッチとタブレット間で接続が確立されると、サービスアビリティー上にプレイヤーリモートが作成され、リクエストを処理します。
public class VideoPlayerServiceAbility extends Ability {
private final PlayerRemote remote = new PlayerRemote(this);
@Override
public IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
return remote.asObject();
}
}
STARTリクエストの場合は、ビデオプレーヤーを起動します。
それ以外の場合は、DistributionNotificationPluginを使用してイベントをパブリッシュし、ビデオプレーヤーで処理できるようにします。
public class PlayerRemote extends RemoteObject implements IRemoteBroker {
static final int REMOTE_COMMAND = 0;
private final Ability ability;
private final DistributeNotificationPlugin distributeNotificationPlugin;
public PlayerRemote(Ability ability) {
super("Player Remote");
this.ability = ability;
distributeNotificationPlugin = DistributeNotificationPlugin.getInstance();
}
@Override
public IRemoteObject asObject() {
return this;
}
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
if (code == REMOTE_COMMAND) {
String command = data.readString();
if (Const.START.equals(command)) {
startVideoPlayer();
} else {
distributeNotificationPlugin.publishEvent(command);
}
return true;
}
return false;
}
private void startVideoPlayer() {
Intent intent = new Intent();
Operation operation =
new Intent.OperationBuilder()
.withBundleName(ability.getBundleName())
.withAbilityName(MainAbility.class.getName())
.withAction(Const.VIDEO_PLAY_ACTION)
.build();
intent.setOperation(operation);
ability.startAbility(intent);
}
}
###ビデオ プレーヤーで受信したイベントを処理
受信したイベントを処理するには、次の手順でビデオプレーヤークラス(MainAbilitySlice)を設定する必要があります。
- イベントをサブクライブ
distributeNotificationPlugin = DistributeNotificationPlugin.getInstance();
distributeNotificationPlugin.setEventListener(this);
distributeNotificationPlugin.subscribeEvent();
- onEventReceiveメソッドを実装してイベントを処理
@Override
public void onEventReceive(String result) {
LogUtil.info(TAG, result);
switch (result) {
case Const.PLAY:
play();
break;
case Const.PAUSE:
pause();
break;
case Const.FORWARD:
play(currentPosition + 1);
break;
case Const.REWIND:
play(currentPosition - 1);
break;
case Const.NEXT:
videoPlayerPlugin.seek();
break;
case Const.PREVIOUS:
videoPlayerPlugin.back();
break;
default:
break;
}
}
private void play(int position) {
progressBar.setVisibility(Component.HIDE);
int maxPosition = avElements.size() - 1;
if (position > maxPosition) {
position = 0;
} else if (position < 0) {
position = maxPosition;
}
currentPosition = position;
AVElement item = avElements.get(position);
String itemText = item.getAVDescription().getTitle().toString();
title.setText(itemText);
playButton.setPixelMap(ResourceTable.Media_pause_button);
videoPlayerPlugin.startPlay(avElements.get(position), surface);
}
private void play() {
videoPlayerPlugin.startPlay();
playButton.setPixelMap(ResourceTable.Media_pause_button);
}
private void pause() {
videoPlayerPlugin.pausePlay();
playButton.setPixelMap(ResourceTable.Media_play_button);
}
デモの結果
デモ環境を設置
- タブレットを PC に接続し、タブレット モジュールをデプロイ
- スマートウォッチ(Galileoバージョン)をPCに接続し、スマートウォッチモジュールをデプロイ
タブレットでマルチデバイスコラボレーション設定を有効にする - Huaweiヘルスケアアプリを使用してスマートウォッチとタブレット間のBlueTooth接続を設定
- タブレットでマルチデバイスコラボレーション設定を有効にする
- Huaweiヘルスケアアプリを使用してスマートウォッチとタブレット間のBlueTooth接続を設定
- 両方の端末を同じWIFIネットワークに接続
- 両方の端末で同じHuawei IDでログイン
##結果
#HarmonyOS #SuperDevice #IoT pic.twitter.com/iZ6kTdzEzu
— huy tran (@knighthedspi) September 3, 2021
スマートウォッチを使用して、上記の操作と同じようにタブレットのビデオプレーヤーを制御できます ☝!
Github のリポジトリも添付しました!自由にクローンしてカスタマイズして、独自のデモを構築してください。