1
1

More than 3 years have passed since last update.

スマートウォッチでタブレット上のビデオプレーヤーを制御できる、ビデオ再生の分散デモ

Last updated at Posted at 2021-09-03

ビデオ再生制御シナリオ

最近では、ユーザーはスマートデバイスでビデオを見ることが多いですが、次のような多くの課題に直面しています。

  • ビデオプレーヤーを制御するには、大画面デバイスを手に持つ必要
  • ビデオの再生中はデバイス上で他のタスクを動作できない

これらの課題を解決するには、以下の解決策を提案します。

  • 軽量のデバイス(スマートウォッチなど)をリモコンとして使用し、大画面デバイスでビデオプレーヤーを操作する 。
  • マルチコラボレーション API を使用してHarmonyOSアプリを開発し、クロスデバイスをデプロイ

image.png

Harmony OSはビデオ再生制御シナリオをどのように解決するか

Harmony OS がサポートするマルチデバイスコラボレーションは次の通リです。
image.png
具体的には、分散スケジューラを使用して、アビリティーの起動または停止できます。

  • 分散スケジューラは、リモートの能力管理機能を提供
    リモートデバイスからFA (Feature Ability, UIがありアビリティを起動できるし、また、PA(Particle Ability,UIがないアビリティ、サービスまたデータのアビリティ)を起動また停止できます。
    image.png

  • アビリティーへの接続または切断
    分散スケジューラは、クロスデバイスPA制御を提供します。リモート PA に接続することで、タスク スケジュール用にクロスデバイス クライアントを取得できます。クロスデバイス タスクが完了すると、リモート PA から切断してこのクライアントを登録解除できます。

  • アビリティーの移行
    分散スケジューラを使用すると、デバイス間のアビリティ移行が可能になります。移行メソッドを呼び出して、ローカルデバイスから指定したリモートデバイスにFAをシームレスに移行できます。
    このシナリオを解決するために、リモート PA に接続してクロスデバイスを制御するHarmony OSの分散スケジューラを使用しました。

    Demoを開発

    プロジェクト構造: 2 つのメイン モジュールがあります

  • スマートウォッチ モジュール

    • リモート コントロール モジュール
    • ウェアラブルデバイスだけでなく、スマートフォンやタブレットにも対応
  • タブレット モジュール

    • ビデオ プレーヤー モジュール
    • スマートウォッチモジュールからのコマンドリクエストを処理して、再生、一時停止、シーク、復帰、スキップなどの操作を実行します。 データ処理フローチャートは、以下になります。 image.png

スマートウォッチモジュールを開発

スマートウォッチモジュールの概要:

  • MainAbility.java: エンドポイントのアビリティ
  • MyApplication.java: アプリケーションクラス
  • controller
    • Const.java:定数値クラス
    • LogUtil.java:ログを記入
    • PlayerRemoteProxy.java: リモートコントロール クラス
  • slice
    • MainAbilitySlice.java:ユーザーと対話するためのメインUIクラス image.png

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_NAMEABILITY_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);
}

ビデオプレーヤーを作成

ビデオプレーヤーのデータ処理フローチャート
image.png
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でログイン

結果

スマートウォッチを使用して、上記の操作と同じようにタブレットのビデオプレーヤーを制御できます ☝!
Github のリポジトリも添付しました!自由にクローンしてカスタマイズして、独自のデモを構築してください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1