[[419218]] 詳細については、以下をご覧ください。 51CTOとHuaweiが共同で構築したHongmengテクノロジーコミュニティ https://harmonyos..com DistributedMusicPlayer分散型音楽プレーヤー導入この例では、主にデータを移行して音楽の分散再生を実行する方法を示します。再生中の曲、再生の進行状況、再生ステータスの維持など、デバイス間での音楽再生の移行が実現します。 エフェクト表示環境を構築するDevEco Studio をインストールします。詳細については、DevEco Studio のダウンロードを参照してください。 DevEco Studio 開発環境をセットアップします。 DevEco Studio 開発環境はネットワーク環境に依存する必要があります。ツールを正常に使用するには、ネットワークに接続する必要があります。次の 2 つの状況に応じて開発環境を構成できます。 インターネットに直接アクセスできる場合は、HarmonyOS SDK をダウンロードするだけで済みます。 ネットワークがインターネットに直接アクセスできず、プロキシ サーバー経由でアクセスする必要がある場合は、「開発環境の構成」を参照してください。 ソースコードをダウンロードし、プロジェクトをインポートします。 コード構造- config.json #グローバル設定ファイル
- │
- ├─ジャワ
- │ └─オーホス
- │ └─サンプル
- │ └─分散音楽プレーヤー
- │ │ メインアビリティ.java
- │ │
- │ ├─スライス
- │ │ MainAbilitySlice.java #プレイヤーのメインアビリティスライス
- │ │
- │ └─ユーティリティ
- │ LogUtil.java #ログユーティリティクラス
- │ PlayerManager.java #プレイヤーマネージャー
- │ PlayerStateListener.java #プレイヤー状態リスナー
- │
- └─リソース
- ├─ベース
- │ ├─要素
- │ │ 文字列.json
- │ │
- │ ├─グラフィック
- │ │ ボタンの背景.xml
- │ │
- │ ├─レイアウト
- │ │ main_ability_slice.xml #プレイヤーページレイアウト
- │ │
- │ └─メディア #ポスター・ボタン画像リソース
- │ アルバム.png
- │ アルバム2.png
- │ bg_blurry.png
- │ アイコン.png
- │ ic_himusic_next.png
- │ ic_himusic_pause.png
- │ ic_himusic_play.png
- │ ic_himusic_previous.png
- │ リモートプレイ選択.png
- │
- └─rawfile #Song メディアリソース
- ホーミー.mp3
- ホームイ.wav
- テクノロジー.mp3
- テクノロジー.wav
実装手順1. デバイス間の移行の標準手順を実装するには、HarmonyOSサンプル - AbilityInteractionデバイス移行を参照してください。 2. プレイヤーマネージャー PlayerManager を実装する 2.1.プレーヤーのステータスを定義します(再生、一時停止、完了、再生中など)。 - プライベート静的最終int PLAY_STATE_PLAY = 0x0000001;
- プライベート静的最終int PLAY_STATE_PAUSE = 0x0000002;
- プライベート静的最終int PLAY_STATE_FINISH = 0x0000003;
- プライベート静的最終int PLAY_STATE_PROGRESS = 0x0000004;
2.2.再生、一時停止、曲の切り替え、再生の進行状況の更新などの基本的なメソッドを実装します。 メディアリソースの設定、再生の進行状況を定期的に更新、再生時間の合計の取得など、補助的な方法もいくつかあります。 Player/Timer/カスタムPlayerStateListener/EventHandlerイベント処理/PlayCallBackプレーヤーコールバッククラスを使用する必要があります - /**
- * 遊ぶ
- */
- パブリックボイドプレイ(){
- 試す {
- 準備が完了している場合
- LogUtil.error(TAG, "準備失敗" );
- 戻る;
- }
- // 再生が開始された場合は true を返します。それ以外の場合はfalseを返します。
- 再生する場合は、
- LogUtil.error(TAG, "再生失敗" );
- 戻る;
- }
- タスクを開始します。
- ハンドラ.sendEvent(PLAY_STATE_PLAY);
- } キャッチ (IllegalArgumentException e) {
- LogUtil.error(TAG, e.getMessage());
- e.printStackTrace();
- }
- }
-
- /**
- * 一時停止
- */
- パブリックボイド一時停止(){
- (!musicPlayer.pause())の場合{
- LogUtil.info(TAG, "一時停止失敗" );
- 戻る;
- }
- // タイミングを停止
- タスクを終了します。
- //
- ハンドラ.sendEvent(PLAY_STATE_PAUSE);
- }
- /**
- * 音楽を切り替える
- *
- * @param uri 音楽 uri
- */
- パブリックvoid switchMusic(String uri) {
- 現在のUri = uri;
- //リソースを設定する
- setResource(現在のUri);
- //遊ぶ
- 遊ぶ();
- }
-
- /**
- * 再生位置を変更する
- * 現在の再生の進行状況を更新します
- *
- * @param currentTime現在の 時間
- */
- パブリックvoid rewindTo( int currentTime) {
- musicPlayer.rewindTo(現在の時間 * 1000);
- }
-
- /**
- *ソースを設定する
- *
- * @param uri 音楽 uri
- */
- パブリックvoid setResource(String uri) {
- LogUtil.info(TAG, "setResource,uri: " + uri);
- 試す {
- RawFileEntry rawFileEntry = context.getResourceManager().getRawFileEntry(uri);
- ベースファイル記述子 baseFileDescriptor = rawFileEntry.openRawFileDescriptor();
- //LogUtil.info(TAG, "setResource,baseFileDescriptor : " + baseFileDescriptor);
- musicPlayer.setSource(baseFileDescriptor) の場合:
- LogUtil.info(TAG, "uri が無効です" );
- 戻る;
- }
- //再生環境を準備し、メディア データをバッファリングします。
- isPrepared = musicPlayer.prepare ();
- LogUtil.info(TAG, "setResource,isPrepared: " + isPrepared);
- //曲名
- 文字列listenerUri = currentUri。部分文字列(currentUri.lastIndexOf( "/" ) + 1、currentUri.lastIndexOf( "." ));
- プレイヤーステートリスナーのUriを設定します。
- LogUtil.info(TAG, "setResource,listenerUri: " +listenerUri);
- } キャッチ (IOException e) {
- LogUtil.error(TAG, "io例外" );
- }
- }
- /**
- * スケジュールされたイベント通知は進行状況バーを更新します
- */
- プライベートvoid startTask() {
- LogUtil.debug(TAG, "startTask" );
- タスクを終了します。
- タイマータスク = 新しいタイマータスク() {
- @オーバーライド
- パブリックボイド実行(){
- ハンドラ.sendEvent(PLAY_STATE_PROGRESS);
- }
- };
- タイマー = 新しいタイマー();
- timer.schedule(timerTask, DELAY_TIME, PERIOD);
- }
-
- プライベートvoid終了タスク() {
- LogUtil.debug(タグ、 "finishTask" );
- if (タイマー != null && タイマータスク != null ) {
- タイマーをキャンセルします。
- タイマー = null ;
- タイマータスク = null ;
- }
- }
2.3.PlayerStateListener プレイヤー状態リスナーには次のメソッドがあります。 onPlaySuccessは再生が成功したときに呼び出されます onPauseSuccessは一時停止時に呼び出されます onPositionChangeは進行状況が変わったときに呼び出されます onMusicFinishedは音楽の再生が完了したときに呼び出されます onUriSetはリソースが設定されたときに呼び出されます - /**
- * プレイヤーステートリスナー
- */
- パブリックインターフェース PlayerStateListener {
- void onPlaySuccess( int totalTime);
-
- void onPauseSuccess();
-
- void onPositionChange( int currentTime);
-
- void onMusicFinished();
-
- void onUriSet(文字列名);
- }
2.4.PlayCallBack プレーヤー コールバック クラスは、Player.IPlayerCallback インターフェイスと次のメソッドを実装します。 onPrepared メディア ファイルの再生準備ができたときに呼び出されます。 onMessage は、プレーヤーのメッセージまたはアラートを受信したときに呼び出されます。 onError は、プレーヤーのエラー メッセージを受信したときに呼び出されます。 ビデオサイズが変更されると、onResolutionChanged が呼び出されます。 再生が完了すると onPlayBackComplete が呼び出されます。 onRewindToComplete Player.rewindTo(long) によって再生位置が変更されたときに呼び出されます。 バッファリングのパーセンテージが更新されると、onBufferingChange が呼び出されます。 新しいタイムドメタデータが利用可能になると、onNewTimedMetaData が呼び出されます。 onMediaTimeIncontinuity は、再生中にエラーが発生したり、Player.rewindTo(long) によって再生位置が変更されたり、再生速度が急激に変更されたりなど、メディアの時間の連続性が中断されたときに呼び出されます。 - /**
- * 再生が完了したとき、再生位置が変更されたとき、およびビデオ サイズが変更されたときに、メディア プレーヤーのコールバックを提供します。
- */
- プライベートクラス PlayCallBack は Player.IPlayerCallback を実装します {
-
- /**
- * メディア ファイルの再生準備ができたときに呼び出されます。
- */
- @オーバーライド
- パブリックvoid onPrepared() {
- LogUtil.info(TAG, "onPrepared" );
- }
-
- /**
- * プレイヤーのメッセージまたはアラートを受信したときに呼び出されます。
- *
- * @パラメータ型
- * @param 追加
- */
- @オーバーライド
- パブリックvoid onMessage( int型、 int追加) {
- LogUtil.info(タグ、 "onMessage" + タイプ + "-" + 追加);
- }
-
-
- /**
- * プレーヤーのエラー メッセージを受信したときに呼び出されます。
- *
- * @param エラータイプ
- * @param エラーコード
- */
- @オーバーライド
- パブリックvoid onError( int errorType, int errorCode) {
- LogUtil.info(タグ、 "onError " + errorType + "-" + errorCode);
- }
-
- /**
- * ビデオサイズが変更されたときに呼び出されます。
- *
- * @param 幅
- * @param 高さ
- */
- @オーバーライド
- パブリックvoid onResolutionChanged( int幅、 int高さ) {
- LogUtil.info(タグ、 "onResolutionChanged" + 幅 + "-" + 高さ);
- }
-
- /**
- * 再生が完了したときに呼び出されます。
- */
- @オーバーライド
- パブリックvoid onPlayBackComplete() {
- //自動的に呼び出されませんか? ? ? ?
- LogUtil.info(タグ、 「onPlayBackComplete----------------」 );
- ハンドラ.sendEvent(PLAY_STATE_FINISH);
-
- }
-
- /**
- * Player.rewindTo(long) によって再生位置が変更されたときに呼び出されます。
- */
- @オーバーライド
- パブリックvoid onRewindToComplete() {
- LogUtil.info(タグ、 "onRewindToComplete" );
- }
-
- /**
- * バッファのパーセンテージが更新されたときに呼び出されます。
- *
- * @param パーセント
- */
- @オーバーライド
- パブリックvoid onBufferingChange( intパーセント){
- LogUtil.info(タグ、 「onBufferingChange:」 + パーセント);
- }
-
- /**
- * 新しいタイムドメタデータが利用可能になったときに呼び出されます。
- *
- * @param mediaTimedMetaData
- */
- @オーバーライド
- パブリックvoid onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {
- LogUtil.info(タグ、 "onNewTimedMetaData" );
- }
-
-
- /**
- * 再生中にエラーが発生したとき、Player.rewindTo(long) によって再生位置が変更されたとき、再生速度が急激に変更されたときなど、メディアの時間連続性が中断されたときに呼び出されます。
- *
- * @param メディア時間情報
- */
- @オーバーライド
- パブリックvoid onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
- LogUtil.info(タグ、 "onNewTimedMetaData" );
- }
- }
3. MainAbilitySliceはPlayerStateListenerとIAbilityContinuationインターフェースを実装する - パブリッククラス MainAbilitySlice は AbilitySlice を拡張し、PlayerStateListener、IAbilityContinuation を実装します {
- ...
3.1. PlayerStateListenerインターフェースメソッドを実装する - @オーバーライド
- パブリックvoid onPlaySuccess( int totalTime) {
- LogUtil.debug(タグ、 "onPlaySuccess" );
- //アイコンを設定する
- musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_pause);
- //合計継続時間テキストを設定する
- this.totalTimeText.setText(getTime(totalTime));
- //プログレスバーを設定する
- slider.setMaxValue(合計時間);
- //現在の曲のポスターを設定する
- musicPosters.setPixelMap(posters[currentPos]);
- }
-
- @オーバーライド
- パブリックvoid onPauseSuccess() {
- LogUtil.debug(TAG, "onPauseSuccess" );
- //アイコンを設定する
- musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_play);
- }
-
- @オーバーライド
- パブリックvoid onUriSet(文字列名) {
- LogUtil.debug(タグ、 "onUriSet" );
- //曲のタイトルを設定する
- musicNameText.setText(名前);
- }
-
- @オーバーライド
- パブリックvoid onPositionChange( int currentTime) {
- 現在の時間 < 合計時間){
- LogUtil.info(TAG, "onPositionChange currentTime = " + currentTime+ ",totalTime=" +totalTime);
- this.currentTime = 現在の時刻;
- //再生時間のテキストを設定する
- this.currentTimeText.setText(getTime(currentTime));
- //プログレスバーの現在の再生時間を設定する
- スライダーの進行状況を設定します。
- }それ以外{
- LogUtil.info(TAG, "onPositionChange, 現在の曲の終了" );
-
- //プレーヤーアイコンを設定する
- musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_play);
- }
- }
-
- /**
- *音楽の再生が終了したときに呼び出されるはずですが、呼び出されていません
- */
- @オーバーライド
- パブリックvoid onMusicFinished() {
- //TODO???????????
- LogUtil.debug(TAG, "onMusicFinished" );
- 現在の位置 = 現在の位置 == 0 ? 1 : 0;
- currentUri = musics[currentPos];
- //曲を切り替える
- playerManager.switchMusic(現在のUri);
- //合計時間
- totalTime=playerManager.getTotalTime();
- }
3.2. IABilityContinuation インターフェース メソッドの実装 - @オーバーライド
- パブリックブールonStartContinuation() {
- LogUtil.debug(TAG, "onStartContinuation" );
- 戻る 真実;
- }
-
- @オーバーライド
- パブリックブールonSaveData(IntentParamsintentParams) {
- LogUtil.debug(タグ、 "onSaveData" );
- //
- パラメータを KEY_CURRENT_TIME に設定します。
- KEY_POSITION に現在の位置を設定します。
- intentParams.setParam(KEY_PLAY_STATE、String.valueOf(playerManager.isPlaying()));
- LogUtil.info(タグ、 "onSaveData:" + 現在の時刻);
- 戻る 真実;
- }
-
- @オーバーライド
- パブリックブールonRestoreData(IntentParamsintentParams) {
- LogUtil.debug(タグ、 "onRestoreData" );
- if (!(intentParams.getParam(KEY_POSITION) のインスタンスがInteger の場合)) {
- 戻る 間違い;
- }
- if (!(intentParams.getParam(KEY_CURRENT_TIME) のインスタンスがInteger の場合)) {
- 戻る 間違い;
- }
- if (!(intentParams.getParam(KEY_PLAY_STATE) のインスタンスの文字列)) {
- 戻る 間違い;
- }
-
- //データを復元し、移行されたパラメータ(再生位置、時間、再生ステータス)を取得します
- 現在の位置 = ( int ) intentParams.getParam(KEY_POSITION);
- 現在の時刻 = ( int ) intentParams.getParam(KEY_CURRENT_TIME);
- オブジェクトオブジェクト = intentParams.getParam(KEY_PLAY_STATE);
-
-
- if (オブジェクトインスタンスの文字列) {
- isPlaying = Boolean.parseBoolean((String) オブジェクト);
- }
- isInteractionPlay = true ;
- LogUtil.info(タグ、 「onRestoreData:」 + 現在の時刻);
- 戻る 真実;
- }
-
- @オーバーライド
- パブリックvoid onCompleteContinuation( int i) {
- 終了();
- }
3.3. ValueChangedListenerImplの進捗値変更のリスナーイベントを定義します。 Slider.ValueChangedListenerインターフェースメソッドを実装する - /**
- *プログレスバーの値の変化を監視するイベント
- */
- プライベートクラス ValueChangedListenerImpl は Slider.ValueChangedListener を実装します {
- @オーバーライド
- パブリックvoid onProgressUpdated(スライダー slider, int progress, boolean fromUser) {
- 現在の時間 = 進行状況;
- }
-
- @オーバーライド
- パブリックvoid onTouchStart(スライダー スライダー) {
- LogUtil.debug(TAG, "onTouchStart" );
- }
-
- @オーバーライド
- パブリックvoid onTouchEnd(スライダー スライダー) {
- LogUtil.debug(TAG, "onTouchEnd" );
- //再生の進行状況を素早く変更する
- playerManager.rewindTo(現在の時間);
- //現在の再生時間
- currentTimeText.setText(getTime(currentTime));
- }
- }
3.4.移行データのKEY、音楽の現在の再生時間、再生中の曲のインデックス(位置)、再生ステータスを定義します。 - プライベート静的最終文字列 KEY_CURRENT_TIME = "main_ability_slice_current_time" ;
- プライベート静的最終文字列 KEY_POSITION = "main_ability_slice_position" ;
- プライベート静的最終文字列 KEY_PLAY_STATE = "main_ability_slice_play_state" ;
- プライベートint現在の位置 = 0;
- プライベート文字列 currentUri;
- //インタラクティブ再生かどうかにかかわらず、 trueはリモート移行リカバリを意味します
- プライベートブール値 isInteractionPlay;
- プライベートint現在の時刻;
- //現在再生中の曲の合計時間
- プライベートint合計時間;
- プライベートブール値 isPlaying;
3.5.再生する音楽の URI を定義します。ここに2曲とそれに対応するポスターがあります。 - プライベート静的最終文字列 URI1 = "resources/rawfile/Technology.wav" ;
- プライベート静的最終文字列 URI2 = "resources/rawfile/Homey.wav" ;
- プライベート最終String[] music = {URI1, URI2};
- プライベート最終int [] ポスター = {ResourceTable.Media_album、ResourceTable.Media_album2};
3.6.onStartはデータの初期化を完了します - @オーバーライド
- パブリックvoid onStart(インテント インテント) {
- super.onStart(インテント);
- super.setUIContent(ResourceTable.Layout_main_ability_slice);
-
- コンポーネントを初期化します。
-
- initMedia();
-
- UI を更新します。
- }
インターフェースコンポーネントを初期化し、対応するボタン監視イベントを実装する 再生または一時停止、前の曲、次の曲、移行、進行状況バーの進行状況変更イベントをリッスンします - /**
- * インターフェースコンポーネントを初期化し、対応するボタン監視イベントを実装する
- * 再生または一時停止、前の曲、次の曲、移行、進行状況バーの進行状況変更イベントをリッスンします
- */
- プライベートvoid initComponents() {
- LogUtil.debug(タグ、 "initComponents" );
- musicNameText = (テキスト) findComponentById(ResourceTable.Id_music_name);
- currentTimeText = (テキスト) findComponentById(ResourceTable.Id_play_progress_time);
- totalTimeText = (テキスト) findComponentById(ResourceTable.Id_play_total_time);
-
- musicPosters = (画像) findComponentById(ResourceTable.Id_music_posters);
-
- musicPlayButton = (画像) findComponentById(ResourceTable.Id_music_play_btn);
- コンポーネント ID を検索します (ResourceTable.Id_remote_play)。クリックされたリスナーを設定します (this::continueAbility);
- コンポーネント ID を検索します (ResourceTable.Id_music_play_prev_btn)。クリックされたリスナーを設定します (this::prevMusic);
- コンポーネント ID を検索します (ResourceTable.Id_music_play_next_btn)。クリックされたリスナーを設定します (this::nextMusic);
-
- musicPlayButton.setClickedListener(this::playOrPauseMusic);
-
- //
- スライダー = (スライダー) findComponentById(ResourceTable.Id_play_progress_bar);
- スライダーにValueChangedListenerを設定します。
- }
-
- プライベートvoid continueAbility(コンポーネントコンポーネント) {
- 試す {
- 継続能力();
- } キャッチ (IllegalStateException e) {
- LogUtil.info(TAG、e.getMessage());
- }
- }
-
- /**
- * 前の
- * @param コンポーネント
- */
- プライベートvoid prevMusic(コンポーネントコンポーネント) {
- 現在の位置 = 現在の位置 == 0 ? 1 : 0;
- currentUri = musics[currentPos];
- //
- playerManager.switchMusic(現在のUri);
- //合計期間
- totalTime=playerManager.getTotalTime();
- }
-
- /**
- * 次
- * @param コンポーネント
- */
- プライベートvoid nextMusic(コンポーネントコンポーネント) {
- 現在の位置 = 現在の位置 == 0 ? 1 : 0;
- currentUri = musics[currentPos];
- //音楽を切り替える
- playerManager.switchMusic(現在のUri);
- //合計期間
- totalTime=playerManager.getTotalTime();
- }
-
- /**
- * 音楽を再生または一時停止する
- * @param コンポーネント
- */
- プライベートvoid playOrPauseMusic(コンポーネントコンポーネント) {
- //
- 再生または一時停止();
- }
-
- /**
- * 再生または一時停止
- */
- プライベートvoid再生または一時停止(){
-
- LogUtil.debug(TAG, "playOrPause,playerManager:" +playerManager);
- 試す {
- //
- プレイヤーマネージャーが再生中の場合(){
- LogUtil.debug(TAG, "playOrPause pause" );
- プレイヤーマネージャーを一時停止します。
- }それ以外{
- //リソースを設定する
- playerManager.setResource(currentUri);
- //進行状況を設定する
- playerManager.rewindTo(現在の時間);
- プレイヤーマネージャーを再生します。
- LogUtil.debug(TAG, "playOrPause 再生" );
- }
- } キャッチ (例外 e) {
- LogUtil.error(TAG, "playOrPause" );
- e.printStackTrace();
- }
- }
3.7.メディアオブジェクトの初期化 現在再生中の曲リソース、プレーヤーマネージャー - /**
- * メディアオブジェクトを初期化する
- * 現在再生中の曲のリソース
- * プレイヤーマネージャー
- */
- プライベートvoid initMedia() {
- LogUtil.debug(タグ、 "initMedia" );
- //現在のメディアURI
- currentUri = musics[currentPos];
- LogUtil.debug(TAG, "initMedia,currentUri:" +currentUri);
- // playerManager を初期化する
- プレイヤーマネージャー = 新しいプレイヤーマネージャー (getApplicationContext()、現在の Uri);
-
- // 弱参照オブジェクトは、参照先オブジェクトの終了、ファイナライズ、およびリサイクルを妨げません。弱参照は、正規のマッピングを実装するために最もよく使用されます。
- WeakReference<PlayerStateListener> playerStateListener = 新しい WeakReference<>(this);
- //ステータスリスナーを設定する
- プレイヤーの状態リスナーを設定します。
- // プレイヤー情報を初期化する
- プレイヤーマネージャーを初期化します。
- LogUtil.debug(タグ、 「initMedia FINISH」 );
- }
3.8.リモート移行後に再生インターフェースを復元する プレーヤーの再生進行状況、再生ステータス、ポスター、現在の時間と合計時間、スライダーの再生進行状況を復元します - /**
- * リモート移行後に再生を再開し、プレーヤーの再生進行状況を復元します
- * UIインターフェースの更新
- */
- プライベートvoid updateUI() {
- LogUtil.debug(タグ、 「updateUI」 );
- //ポスター
- musicPosters.setPixelMap(posters[currentPos]);
- //現在の時刻と合計継続時間
- currentTimeText.setText(getTime(currentTime));
- totalTimeText.setText(getTime(playerManager.getTotalTime()));
- // プレイの進行状況
- slider.setMaxValue(playerManager.getTotalTime());
- スライダーの進行状況を設定します。
-
- //合計時間
- totalTime=playerManager.getTotalTime();
-
- //リモート移行リカバリ
- if (isInteractionPlay) {
- LogUtil.debug(TAG, "remotePlay,rewindTo:" +currentTime);
- playerManager.rewindTo(現在の時間);
- if (!isPlaying) {
- 戻る;
- }
- //遊ぶ
- プレイヤーマネージャーを再生します。
- }
- }
問題の概要1. 音楽の再生が終了したときに onMusicFinished を呼び出す必要がありますが、ほとんどの場合は呼び出されず、たまにしか呼び出されません。コンピュータのパフォーマンスが追いつかないからでしょうか? 2. ソースコードでアプリケーションを起動した後、クリックしてもビデオが再生されない問題を最適化しました。 3. 現在の曲を再生した後に再生アイコンを更新するようにプレーヤーを最適化しました 4. 関連メモを追加 添付ファイルからDistributedMusicPlayer.zipをダウンロードしてください。 詳細については、以下をご覧ください。 51CTOとHuaweiが共同で構築したHongmengテクノロジーコミュニティ https://harmonyos..com |