diff --git a/README.en.md b/README.en.md
index ec25facf1ecdf6326e616ff63c452e5db9f50e17..db9a47240acb9b7b96ba9fabb119ff7bc7bbf44d 100644
--- a/README.en.md
+++ b/README.en.md
@@ -1,98 +1,131 @@
-# Synchronous Audio and Video Playback
-
-### Overview
-
-This sample showcases how to implement synchronous audio and video playback in scenarios such as Bluetooth earphones. The synchronization is implemented by calculating the latency of audio and video frames based on video decoding. With the audio and video synchronization module, users can enjoy better video experience in high-latency scenarios.
-
-### Preview
-
-| App UI | Playback |
-|------------------------------------------------------------|------------------------------------------------|
-|  |  |
+# Achieving Audio-Video Synchronization Playback Effect
+
+## Introduction
+This example is based on video decoding and achieves audio-video synchronization adaptation by calculating the delay of audio and video frames. It solves the audio-video desynchronization issue in scenarios such as local video playback, network video playback, and recorded video playback. When developers implement video decoding and playback functions, integrating the audio-video synchronization module can maintain synchronized playback of video images and audio, providing users with a better viewing experience in high-latency scenarios.
+
+## Effect Preview
+| Application Interface | Network Video Playback Page | Recording Page | Audio-Video Synchronization Playback Page |
+|-------------------------------------------------------|---------------------------------------------------------|--------------------------------------------------------|------------------------------------------------------|
+|
|
|
|
|
+
+## Usage Instructions
+1. Open the application, where three scenarios are available: local video audio-video synchronization, network video audio-video synchronization, and recorded video audio-video synchronization.
+2. Click to enter the local video audio-video synchronization scenario and select a specific video from the gallery.
+3. Enter the audio-video synchronization page. The playback page provides x1, x2, and x3 fast-forward options.
+4. Click to enter the network video audio-video synchronization scenario. A fixed network video is provided; click to play it.
+5. Enter the audio-video synchronization page, and follow the same operation as step 3.
+6. Click to enter the recorded video audio-video synchronization scenario.
+7. Enter the recording page. After recording a video, the video will be saved to the gallery. The gallery will open automatically; select the specific video, and follow the same operation as step 3.
+
+## Project Directory
+```
+├── entry/src/main/cpp // Native Layer
+│ ├── capabilities // Capability Interfaces and Implementations
+│ │ ├── include // Capability Interfaces
+│ │ ├── AudioDecoder.cpp // Audio Decoding Implementation
+│ │ ├── AudioEncoder.cpp // Audio Encoding Implementation
+│ │ ├── Demuxer.cpp // Demuxing Implementation
+│ │ ├── Muxer.cpp // Muxing Implementation
+│ │ ├── VideoDecoder.cpp // Video Decoding Implementation
+│ │ └── VideoEncoder.cpp // Video Encoding Implementation
+│ ├── common // Common Module
+│ │ ├── dfx // Log Implementation
+│ │ ├── SampleCallback.cpp // Codec Callback Implementation
+│ │ ├── SampleCallback.h // Codec Callback Definition
+│ │ └── SampleInfo.h // Common Class for Function Implementation
+│ ├── player // Native Layer Player
+│ │ ├── Player.cpp // Implementation of Native Layer Playback Function Call Logic
+│ │ ├── Player.h // Interface of Native Layer Playback Function Call Logic
+│ │ ├── PlayerNative.cpp // Entry Implementation of Native Layer Playback
+│ │ └── PlayerNative.h // Interface of Native Layer Playback
+│ ├── recorder // Recording Interface
+│ │ ├── Recorder.cpp // Recording Function Interface Implementation
+│ │ ├── Recorder.h // Recording Function Interface Definition
+│ │ ├── RecorderNative.cpp // Recording Interface Call Entry
+│ │ └── RecorderNative.h // Call Entry Definition
+│ ├── render // Rendering Module Interfaces and Implementations
+│ │ ├── include // Rendering Module Interfaces
+│ │ ├── PluginManager.cpp // Rendering Module Management Implementation
+│ │ └── PluginRender.cpp // Rendering Logic Implementation
+│ ├── types // Interfaces Exposed by Native Layer
+│ │ ├── libplayer // Interfaces Exposed by Playback Module to UI Layer
+│ │ └── librecorder // Interfaces Exposed by Recording Module to UI Layer
+│ └── CMakeLists.txt // Compilation Entry
+├── ets // UI Layer
+│ ├── common // Common Module
+│ │ ├── utils // Common Utility Classes
+│ │ │ ├── CameraCheck.ets // Camera Utility Class
+│ │ │ ├── DateTimeUtils.ets // Date and Time Utility Class
+│ │ │ ├── FileUtil.ets // File Utility Class
+│ │ │ ├── Logger // Log Utility Class
+│ │ │ ├── PermissionUtil // Permission Utility Class
+│ │ │ ├── RecorderUtil // Video Recording Utility Class
+│ │ │ └── RouterUtil // Routing Utility Class
+│ │ └── CommonConstants.ets // Parameter Constants
+│ ├── entryability // Application Entry
+│ │ └── EntryAbility.ets // Entry Function Class
+│ ├── entrybackupability // Application Background
+│ │ └── EntryBackupAbility.ets // Background Management Class
+│ ├── model // Data Interaction Class
+│ │ └── CameraDateModel.ets // Camera Parameter Data Class
+│ ├── pages // Pages
+│ │ ├── Index.ets // Home Page/Scene Selection Page
+│ │ ├── NetworkVideo.ets // Network Video Playback Page
+│ │ ├── PlayerSync.ets // Audio-Video Synchronization Playback Page
+│ │ └── Recorder.ets // Camera Recording Page
+├── resources // Used to Store Resource Files Used by the Application
+│ ├── base // Resource files under this directory will be assigned unique IDs
+│ │ ├── element // Used to Store Fonts and Colors
+│ │ ├── media // Used to Store Images
+│ │ └── profile // Application Entry Home Page
+│ ├── en_US // When the device language is American English, resources under this directory are matched first
+│ └── zh_CN // When the device language is Simplified Chinese, resources under this directory are matched first
+└── module.json5 // Module Configuration Information
+```
-### How to Use
+## Specific Implementation
-1. Record videos with the camera or push video files to Gallery.
-2. Open the app, touch the play button, and select a video from Gallery to play.
-### Project Directory
+### Video Decoding Part
-```
-├──entry/src/main/cpp // Native layer
-│ ├──capbilities // Capability interfaces and implementation
-│ │ ├──include // Capability interfaces
-│ │ ├──AudioDecoder.cpp // Audio decoder implementation
-│ │ ├──Demuxer.cpp // Demuxer implementation
-│ │ ├──VideoDecoder.cpp // Video decoder implementation
-│ ├──common // Common modules
-│ │ ├──dfx // Logs
-│ │ ├──SampleCallback.cpp // Codec callback implementation
-│ │ ├──SampleCallback.h // Codec callback definition
-│ │ └──SampleInfo.h // Common classes for function implementation
-│ ├──player // Native layer
-│ │ ├──Player.cpp // Player invocation logic implementation at the native layer
-│ │ ├──Player.h // Player invocation logic interfaces at the native layer
-│ │ ├──PlayerNative.cpp // Player entry implementation at the native layer
-│ │ └──PlayerNative.h // Player interfaces at the native layer
-│ ├──render // Interfaces and implementation of the display module
-│ │ ├──include // Display module interfaces
-│ │ ├──PluginManager.cpp // Display module management implementation
-│ │ └──PluginRender.cpp // Display logic implementation
-│ ├──types // Interfaces exposed by the native layer
-│ │ ├──libplayer // Interfaces exposed by the player to the UI layer
-│ └──CMakeLists.txt // Compilation entry
-├──ets // UI layer
-│ ├──common // Common modules
-│ │ └──CommonConstants.ets // Common constants
-│ ├──entryability // App entry
-│ │ └──EntryAbility.ets
-│ ├──entrybackupability
-│ │ └──EntryBackupAbility.ets
-│ └──pages // Pages contained in the EntryAbility
-│ └──Index.ets // Home page/Playback page
-├──resources // Static resources of the app
-│ ├──base // Resource files in this directory are assigned unique IDs.
-│ │ ├──element // Fonts and colors
-│ │ ├──media // Images
-│ │ └──profile // Home page of the app entry
-│ ├──en_US // Resources in this directory are preferentially matched when the device language is American English.
-│ └──zh_CN // Resources in this directory are preferentially matched when the device language is simplified Chinese.
-└──module.json5 // Module configuration
-```
+1. After the user clicks the play button, a click event is triggered to call the PhotoViewPicker() interface. This function launches the gallery's file selection module and retrieves the path of the video selected by the user.
+2. Upon successful file selection by the user, the playNative() interface calls the PlayerNative::Play() function to initialize and invoke the decoding module to start decoding.
+3. After the decoder starts, the input callback is triggered. The data to be decoded is filled into OH_AVBuffer, and the PushInputBuffer interface is called to send the data to the decoder for decoding. At least one XPS frame must be pushed after each start.
+4. Each time the decoder decodes a frame, the output callback is triggered once. The user needs to promptly call the rendering or release interface to return the buffer to the decoder. Since the decoder has an upper limit on the number of buffers, timely return is required; otherwise, the decoder will stop working once the limit is reached until a buffer is returned.
+5. At the end of playback, the napi_call_function() interface in Callback() is triggered to execute the corresponding callback event.
-### How to Implement
+### Video Recording:
-##### Video Decoding
+1. Obtain camera captured data through cameraInput and create a camera input.
+2. Create previewOutput to get the preview output stream, connect it via the surfaceId of XComponent, and render to XComponent.
+3. Create a video recording output stream VideoOutput through surfaceId to output to a file.
+4. Specific implementations based on the Recorder method are encapsulated in RecordController.ets, and those based on the AVCodec method are encapsulated in AVCodecController.ets.
-1. If you tap the play button, a tap event is triggered to call the **PhotoViewPicker()** API. This function calls the picker module of Gallery to obtain the path of the tapped video.
-2. If you select a file, the **playNative()** API calls the **PlayerNative::Play()** function to initialize the player and calls the decoding module to start decoding.
-3. If you start the decoder, the input callback is called to fill the data to be decoded in **OH_AVBuffer**. The **PushInputBuffer** API is called to send the data to the decoder. Each time the decoder is started, the XPS information frame needs to be pushed at least once.
-4. Each time the decoder decodes a frame, the output callback is called. You need to call the display or freeing API in time to free the buffer to the decoder. Because the maximum size of the buffer is limited, the buffer needs to be freed in time. Otherwise, the decoder stops working when the buffer is full.
-5. When the playback ends, the **napi_call_function()** API in **Callback()** is called to execute the corresponding callback event.
+### Audio-Video Synchronization Part
-##### Audio and Video Synchronization
+1. When a video frame is received, call the OH_AudioRenderer_GetTimestamp() interface to obtain information such as the audio rendering position.
+2. Before the audio starts, to avoid stutters and other issues, synchronization is temporarily disabled, and video frames are directly rendered.
+3. After the audio starts, calculate the delay based on the video frame PTS and audio rendering position, and select the audio-video synchronization strategy according to the delay:
+ - If the video frame is more than 40ms behind the audio frame, discard this video frame directly.
+ - If the video frame is less than 40ms behind the audio frame, render it directly.
+ - If the video frame is ahead of the audio frame, perform gradual synchronization and wait for a period of time before rendering.
-1. When a video frame is received, call **OH_AudioRenderer_GetTimestamp()** to obtain information such as the audio rendering position.
-2. Before audio is started, video frames are directly sent for display to avoid stuttering, without waiting for synchronization.
-3. After audio is started, the delay is calculated based on the presentation time stamp (PTS) of the video frame and the audio rendering position, and the delay determines the audio and video synchronization policy.
-- If a video frame is lagging behind an audio frame for 40 ms or longer, the video frame is discarded.
-- If a video frame is lagging behind an audio frame for less than 40 ms, the video frame is directly displayed.
-- If a video frame is ahead of an audio frame, progressive synchronization is performed and the video frame waits for a period of time before being displayed.
-### Permissions
+## Related Permissions
-- N/A
+- Allow the app to use the camera: ohos.permission.CAMERA.
+- Allow the app to use the microphone: ohos.permission.MICROPHONE.
-### Dependencies
+## Dependencies
-- N/A
+- None involved.
-### Constraints
+## Constraints and Limitations
-1. The sample is only supported on Huawei phones with standard systems.
+1. This example only supports running on standard systems, compatible devices: Huawei phones.
-2. The HarmonyOS version must be HarmonyOS 5.0.5 Release or later.
+2. HarmonyOS version: HarmonyOS 6.0.0 Release or above.
-3. The DevEco Studio version must be DevEco Studio 5.0.5 Release or later.
+3. DevEco Studio version: DevEco Studio 6.0.0 Release or above.
-4. The HarmonyOS SDK version must be HarmonyOS 5.0.5 Release SDK or later.
\ No newline at end of file
+4. HarmonyOS SDK version: HarmonyOS 6.0.0 Release SDK or above.
\ No newline at end of file
diff --git a/README.md b/README.md
index 6116754dc80c704f072215d604dd36842c61ea8b..1049d8d75d18f2c166eea5ed575dd1de8d78841f 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@
本示例基于视频解码,通过计算音视频帧的延时进行音画同步适配,解决在本地视频播放、网络视频播放和录制视频播放等场景的音画不同步问题。开发者在实现解码播放视频功能时,加入音画同步模块,保持视频画面和音频同步播放,让用户在高时延场景下观看视频有更好的体验。
## 效果预览
-| 应用界面 | 播放展示 |
-|------------------------------------------------------|------------------------------------------|
-|  |  |
+| 应用首页 | 网络视频播放页面 | 录制页面 | 音画同步播放页面 |
+|--------------------------------------------|------------------------------------------------|-----------------------------------------------|---------------------------------------------|
+|
|
|
|
|
## 使用说明
1. 打开应用,有三个场景供选择:本地视频音画同步场景、网络视频音画同步场景、录制视频音画同步场景。
@@ -71,7 +71,7 @@
│ ├──pages // 页面
│ │ ├──Index.ets // 首页/场景选择页面
│ │ ├──NetworkVideo.est // 网络视频播放页面
-│ │ ├──Player.ets // 音画同步播放页面
+│ │ ├──PlayerSync.ets // 音画同步播放页面
│ │ └──Recorder.ets // 相机录制页面
├──resources // 用于存放应用所用到的资源文件
│ ├──base // 该目录下的资源文件会被赋予唯一的ID
diff --git a/build-profile.json5 b/build-profile.json5
index 93f46b4346c1465625afe05c97b49721b7eec704..1ad15dcb9ced803afb8f5662a95be26f42ecd95d 100644
--- a/build-profile.json5
+++ b/build-profile.json5
@@ -20,13 +20,13 @@
"name": "default",
"type": "HarmonyOS",
"material": {
- "certpath": "/Users/zhouxinyu/.ohos/config/default_AudioToVideoSync_td3IKXDEbx6A2gXt8PwKN38_GHq_sTdlRJUnLze3lzw=.cer",
+ "certpath": "/Users/zhouxinyu/.ohos/config/default_AudioToVideoSync_csr3xBkMdc0QRnHvLVoAt_o1DwKWd62quW9yvtYZlkQ=.cer",
"keyAlias": "debugKey",
- "keyPassword": "0000001B7B038B25EBD58E9F651E51B60315BD30B416497D1B66FEF36199B3DAE8908DC4BA2E43F13D68D2",
- "profile": "/Users/zhouxinyu/.ohos/config/default_AudioToVideoSync_td3IKXDEbx6A2gXt8PwKN38_GHq_sTdlRJUnLze3lzw=.p7b",
+ "keyPassword": "0000001B3EB1133810407D43499B669042F9CCB7C9DBC235846B921F6FA992E5513D3ABF3E68789A53EF18",
+ "profile": "/Users/zhouxinyu/.ohos/config/default_AudioToVideoSync_csr3xBkMdc0QRnHvLVoAt_o1DwKWd62quW9yvtYZlkQ=.p7b",
"signAlg": "SHA256withECDSA",
- "storeFile": "/Users/zhouxinyu/.ohos/config/default_AudioToVideoSync_td3IKXDEbx6A2gXt8PwKN38_GHq_sTdlRJUnLze3lzw=.p12",
- "storePassword": "0000001B6703569107C7822567DDDECD4C2FA9552A8F7A2CCA682BC51D863BC6ECB31050B08F40932560B1"
+ "storeFile": "/Users/zhouxinyu/.ohos/config/default_AudioToVideoSync_csr3xBkMdc0QRnHvLVoAt_o1DwKWd62quW9yvtYZlkQ=.p12",
+ "storePassword": "0000001B2C19D41BB0BB1624F9F779B1E6510D7A75EC8FCE9543A247A63511AB117D56B6249DFCCD155908"
}
}
],
@@ -34,8 +34,8 @@
{
"name": "default",
"signingConfig": "default",
- "compatibleSdkVersion": "5.0.5(17)",
- "targetSdkVersion": "5.0.5(17)",
+ "compatibleSdkVersion": "6.0.0(20)",
+ "targetSdkVersion": "6.0.0(20)",
"runtimeOS": "HarmonyOS",
}
]
diff --git a/entry/src/main/cpp/capbilities/include/AudioCapturer.h b/entry/src/main/cpp/capbilities/include/AudioCapturer.h
index 1fd8898a78f34eca77383f01b55d99978c6ea731..83285b42722263cfb735fff067be404fb788e6d9 100644
--- a/entry/src/main/cpp/capbilities/include/AudioCapturer.h
+++ b/entry/src/main/cpp/capbilities/include/AudioCapturer.h
@@ -37,4 +37,4 @@ private:
};
-#endif // AVCODECVIDEO_AUDIOCAPTURER_H
+#endif
diff --git a/entry/src/main/cpp/capbilities/include/AudioDecoder.h b/entry/src/main/cpp/capbilities/include/AudioDecoder.h
index e94f510437360f8dda4ff888262000b324656181..bdb2d1e2ee8977a6e0d5c0db313b0a289866926b 100644
--- a/entry/src/main/cpp/capbilities/include/AudioDecoder.h
+++ b/entry/src/main/cpp/capbilities/include/AudioDecoder.h
@@ -41,4 +41,4 @@ private:
bool isAVBufferMode = false;
OH_AVCodec *decoder;
};
-#endif // AUDIODECODER_H
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/entry/src/main/cpp/capbilities/include/AudioEncoder.h b/entry/src/main/cpp/capbilities/include/AudioEncoder.h
index 1261bf12e07162b548ce3253a70ab32008688691..8f96770f849a5fff8b9fd39ad279bb7e38653522 100644
--- a/entry/src/main/cpp/capbilities/include/AudioEncoder.h
+++ b/entry/src/main/cpp/capbilities/include/AudioEncoder.h
@@ -45,4 +45,4 @@ private:
OH_AVCodec *encoder_;
};
-#endif // AVCODECVIDEO_AUDIOENCODER_H
+#endif
diff --git a/entry/src/main/cpp/capbilities/include/Demuxer.h b/entry/src/main/cpp/capbilities/include/Demuxer.h
index 0520dc2a793f229834012a22d13d61eeeddf3a8f..3677304cd248bd2afc1af5f3d7c7a03a3877d04e 100644
--- a/entry/src/main/cpp/capbilities/include/Demuxer.h
+++ b/entry/src/main/cpp/capbilities/include/Demuxer.h
@@ -42,4 +42,4 @@ private:
int32_t audioTrackId;
};
-#endif // DEMUXER_H
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/entry/src/main/cpp/capbilities/include/VideoDecoder.h b/entry/src/main/cpp/capbilities/include/VideoDecoder.h
index aa9349e63734f67add8f1c5617f4cd9c9afdd717..7cd69b1a5086d9115024e86d1a2a3756c2b7c75c 100644
--- a/entry/src/main/cpp/capbilities/include/VideoDecoder.h
+++ b/entry/src/main/cpp/capbilities/include/VideoDecoder.h
@@ -42,4 +42,4 @@ private:
bool isAVBufferMode = false;
OH_AVCodec *decoder;
};
-#endif // VIDEODECODER_H
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/entry/src/main/cpp/capbilities/include/VideoEncoder.h b/entry/src/main/cpp/capbilities/include/VideoEncoder.h
index 23ba1826327ae4bbd07f3b4a603caa5ebb16d9c4..640641feab26ea1dd76c3e9756195dd8419174e3 100644
--- a/entry/src/main/cpp/capbilities/include/VideoEncoder.h
+++ b/entry/src/main/cpp/capbilities/include/VideoEncoder.h
@@ -45,4 +45,4 @@ private:
bool isAVBufferMode_ = false;
OH_AVCodec *encoder_ = nullptr;
};
-#endif // VIDEOENCODER_H
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/entry/src/main/cpp/common/SampleCallback.cpp b/entry/src/main/cpp/common/SampleCallback.cpp
index e9123255f227df32cd80282ba14f59807d94c8b2..6df14f5c403a859ffc0bd5e3e14c238602aa26c7 100644
--- a/entry/src/main/cpp/common/SampleCallback.cpp
+++ b/entry/src/main/cpp/common/SampleCallback.cpp
@@ -15,18 +15,18 @@
#include "SampleCallback.h"
#include "AVCodecSampleLog.h"
+#include "Player.h"
namespace {
constexpr int LIMIT_LOGD_FREQUENCY = 50;
constexpr int32_t BYTES_PER_SAMPLE_2 = 2;
-}
+} // namespace
// Custom write data function
int32_t SampleCallback::OnRenderWriteData(OH_AudioRenderer *renderer, void *userData, void *buffer, int32_t length) {
(void)renderer;
(void)length;
CodecUserData *codecUserData = static_cast(userData);
-
// Write the data to be played to the buffer by length
uint8_t *dest = (uint8_t *)buffer;
size_t index = 0;
@@ -36,17 +36,26 @@ int32_t SampleCallback::OnRenderWriteData(OH_AudioRenderer *renderer, void *user
dest[index++] = codecUserData->renderQueue.front();
codecUserData->renderQueue.pop();
}
+ if (index < length) {
+ memset(dest + index, 0, length - index);
+ codecUserData->sampleInfo->playDoneCallback(codecUserData->sampleInfo->playDoneCallbackData,
+ PlayStatus::Buffering);
+ } else {
+ codecUserData->sampleInfo->playDoneCallback(codecUserData->sampleInfo->playDoneCallbackData,
+ PlayStatus::Playing);
+ }
+
AVCODEC_SAMPLE_LOGD("render BufferLength:%{public}d Out buffer count: %{public}u, renderQueue.size: %{public}u "
"renderReadSize: %{public}u",
length, codecUserData->outputFrameCount, (unsigned int)codecUserData->renderQueue.size(),
(unsigned int)index);
-
- codecUserData->frameWrittenForSpeed += length / codecUserData->speed / codecUserData->sampleInfo->
- audioChannelCount / BYTES_PER_SAMPLE_2;
- codecUserData->currentPosAudioBufferPts = codecUserData->endPosAudioBufferPts - codecUserData->
- renderQueue.size() / codecUserData->sampleInfo->audioSampleRate / codecUserData->sampleInfo->
- audioChannelCount / BYTES_PER_SAMPLE_2;
-
+
+ codecUserData->frameWrittenForSpeed +=
+ length / codecUserData->speed / codecUserData->sampleInfo->audioChannelCount / BYTES_PER_SAMPLE_2;
+ codecUserData->currentPosAudioBufferPts =
+ codecUserData->endPosAudioBufferPts - codecUserData->renderQueue.size() /
+ codecUserData->sampleInfo->audioSampleRate /
+ codecUserData->sampleInfo->audioChannelCount / BYTES_PER_SAMPLE_2;
if (codecUserData->renderQueue.size() < length) {
codecUserData->renderCond.notify_all();
}
diff --git a/entry/src/main/cpp/common/SampleInfo.h b/entry/src/main/cpp/common/SampleInfo.h
index 8e86be7b2949889b9df979bda1329556d8bb28d7..e26bab8c83cfa6ba3688dbc2e9dfde808bd7316d 100644
--- a/entry/src/main/cpp/common/SampleInfo.h
+++ b/entry/src/main/cpp/common/SampleInfo.h
@@ -73,7 +73,7 @@ struct SampleInfo {
OHNativeWindow *window = nullptr;
- void (*playDoneCallback)(void *context) = nullptr;
+ void (*playDoneCallback)(void *context, int32_t playStatus) = nullptr;
void *playDoneCallbackData = nullptr;
};
diff --git a/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h b/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h
index 59f35de8137e629727beba5245a2b9f5187f0e8e..1d518a3b67cbddb4004a4230e66fba5ecc370fd0 100644
--- a/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h
+++ b/entry/src/main/cpp/common/dfx/error/AVCodecSampleError.h
@@ -21,4 +21,4 @@ enum AVCodecSampleError : int {
AVCODEC_SAMPLE_ERR_ERROR = -1,
};
-#endif // AVCODEC_SAMPLE_ERROE_H
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/entry/src/main/cpp/player/Player.cpp b/entry/src/main/cpp/player/Player.cpp
index 2ca0568353344e6df2a1e7c4bb51aea0b23e76e4..4ab399565b1ba63e3188230f52ead21fb00d7fdf 100644
--- a/entry/src/main/cpp/player/Player.cpp
+++ b/entry/src/main/cpp/player/Player.cpp
@@ -31,6 +31,7 @@ constexpr int32_t BYTES_PER_SAMPLE_2 = 2; // 2 bytes pe
using namespace std::chrono_literals;
} // namespace
+
Player::~Player() { Player::StartRelease(); }
int32_t Player::CreateAudioDecoder() {
@@ -152,6 +153,7 @@ int32_t Player::Start() {
}
AVCODEC_SAMPLE_LOGI("Succeed");
doneCond.notify_all();
+ sampleInfo.playDoneCallback(sampleInfo.playDoneCallbackData, PlayStatus::Playing);
return AVCODEC_SAMPLE_ERR_OK;
}
@@ -166,9 +168,10 @@ void Player::SetSpeed(float speed) {
audioDecContext->speed = speed;
}
-void Player::Stop(){
- StartRelease();
-}
+void Player::Stop() { StartRelease(); }
+
+void Player::UpdatePlayerStatus(int32_t playStatus){};
+
void Player::StartRelease() {
if (audioRenderer) {
@@ -202,7 +205,6 @@ void Player::ReleaseThread() {
void Player::Release() {
std::lock_guard lock(mutex);
isStarted = false;
-
// Clear the queue
while (audioDecContext && !audioDecContext->renderQueue.empty()) {
audioDecContext->renderQueue.pop();
@@ -239,7 +241,7 @@ void Player::Release() {
}
doneCond.notify_all();
// Trigger the callback
- sampleInfo.playDoneCallback(sampleInfo.playDoneCallbackData);
+ sampleInfo.playDoneCallback(sampleInfo.playDoneCallbackData, PlayStatus::Stopped);
AVCODEC_SAMPLE_LOGI("Succeed");
}
diff --git a/entry/src/main/cpp/player/Player.h b/entry/src/main/cpp/player/Player.h
index e4000eeb13c0ff239688761554f9e8350c46f075..27d5db9fd0893e70b3046e36a885101868c8db45 100644
--- a/entry/src/main/cpp/player/Player.h
+++ b/entry/src/main/cpp/player/Player.h
@@ -28,6 +28,8 @@
#include "SampleInfo.h"
#include "PluginManager.h"
+enum PlayStatus { Stopped = 0, Playing = 1, Buffering = 2 };
+
class Player {
public:
Player(){};
@@ -42,6 +44,7 @@ public:
int32_t Start();
void SetSpeed(float speed);
void Stop();
+ void UpdatePlayerStatus(int32_t playStatus);
private:
void VideoDecInputThread();
diff --git a/entry/src/main/cpp/player/PlayerNative.cpp b/entry/src/main/cpp/player/PlayerNative.cpp
index da6dac06c6b84cbe7beed96d1ce29941e42d07c0..9de825cf7637901ecd96800f53289fabd0515736 100644
--- a/entry/src/main/cpp/player/PlayerNative.cpp
+++ b/entry/src/main/cpp/player/PlayerNative.cpp
@@ -23,13 +23,15 @@
struct CallbackContext {
napi_env env = nullptr;
napi_ref callbackRef = nullptr;
+ int32_t playStatus = 1;
};
-void Callback(void *asyncContext) {
+void Callback(void *asyncContext, int32_t playStatus) {
uv_loop_s *loop = nullptr;
CallbackContext *context = (CallbackContext *)asyncContext;
napi_get_uv_event_loop(context->env, &loop);
uv_work_t *work = new uv_work_t;
+ context->playStatus = playStatus;
work->data = context;
uv_queue_work(
loop, work, [](uv_work_t *work) {},
@@ -39,12 +41,15 @@ void Callback(void *asyncContext) {
// manage napi_value lifeCycle,avoid memory leaks.
napi_open_handle_scope(context->env, &scope);
napi_value callback = nullptr;
+ napi_value napiPlayStatus;
+ napi_create_int32(context->env, context->playStatus, &napiPlayStatus);
+ napi_value args[1] = {napiPlayStatus};
napi_get_reference_value(context->env, context->callbackRef, &callback);
// callBack to UI side.
- napi_call_function(context->env, nullptr, callback, 0, nullptr, nullptr);
+ napi_call_function(context->env, nullptr, callback, 1, args, nullptr);
napi_close_handle_scope(context->env, scope);
- delete context;
- delete work;
+// delete context;
+// delete work;
});
}
diff --git a/entry/src/main/cpp/render/include/PluginManager.h b/entry/src/main/cpp/render/include/PluginManager.h
index ea1456416d33684c28b457f51fe54cac081f1f67..102b754869863f6688d6895f498fea7b7cbedf40 100644
--- a/entry/src/main/cpp/render/include/PluginManager.h
+++ b/entry/src/main/cpp/render/include/PluginManager.h
@@ -45,4 +45,4 @@ private:
std::unordered_map pluginRenderMap;
};
} // namespace NativeXComponentSample
-#endif // NATIVE_XCOMPONENT_PLUGIN_MANAGER_H
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/entry/src/main/cpp/types/libplayer/index.d.ts b/entry/src/main/cpp/types/libplayer/index.d.ts
index 78168ea78808696206b447ee7c4c643e4b649df4..927d3e36946545a1dd4dcbea8f48270cb5922a90 100644
--- a/entry/src/main/cpp/types/libplayer/index.d.ts
+++ b/entry/src/main/cpp/types/libplayer/index.d.ts
@@ -19,7 +19,7 @@ export const playNative: (
inputFileSize: number,
isLocal: boolean,
networkUri: string,
- cbFn: () => void
+ cbFn: (status:number) => void
) => void;
export const ratePlay: (
diff --git a/entry/src/main/ets/common/CommonConstants.ets b/entry/src/main/ets/common/CommonConstants.ets
index 78cdbcd3380e19270a88c04c968fe6a911cbfd1f..ba39ef159d96db5f9f0954ea1f0ca250c995e9ba 100644
--- a/entry/src/main/ets/common/CommonConstants.ets
+++ b/entry/src/main/ets/common/CommonConstants.ets
@@ -14,7 +14,6 @@
*/
import { camera } from "@kit.CameraKit";
-import { Permissions } from "@kit.AbilityKit";
export class CommonConstants {
/**
@@ -60,12 +59,12 @@ export class CommonConstants {
/**
* Player Page name.
*/
- static readonly PLAYER_PAGE: string = 'Player';
+ static readonly PLAYER_PAGE: string = 'PlayerSync';
/**
* Player Page name.
*/
- static readonly NETWORKVIDEO_PAGE: string = 'NetworkVideo';
+ static readonly NETWORK_VIDEO_PAGE: string = 'NetworkVideo';
/**
* Recorder Page name.
@@ -81,7 +80,7 @@ export class CommonConstants {
/**
* video uri.
*/
- static readonly VIDEO_URI = "https://consumer.huawei.com/content/dam/huawei-cbg-site/cn/mkt/pdp/headphones/freebuds-lipstick-2/videos/huawei-freebuds-lipstick-2-creative-design-id.mp4"
+ static readonly VIDEO_URI = 'https://consumer.huawei.com/content/dam/huawei-cbg-site/cn/mkt/pdp/headphones/freebuds-lipstick-2/videos/huawei-freebuds-lipstick-2-creative-design-id.mp4'
/**
* 30 FPS.
diff --git a/entry/src/main/ets/common/utils/FileUtil.ets b/entry/src/main/ets/common/utils/FileUtil.ets
index d2bf73c83fcc050d1990956f9c70441a062989e9..0813e38cdca94c508bdc4ee778d1c5b70d6e634a 100644
--- a/entry/src/main/ets/common/utils/FileUtil.ets
+++ b/entry/src/main/ets/common/utils/FileUtil.ets
@@ -14,8 +14,18 @@
*/
import { photoAccessHelper } from "@kit.MediaLibraryKit";
+import DateTimeUtil from "./DateTimeUtils";
+import { fileIo } from "@kit.CoreFileKit";
+import { CommonConstants } from "../CommonConstants";
+import { Params, SCENE } from "./RouterUtil";
+import { CameraDataModel } from "../../model/CameraDateModel";
+import Logger from "./Logger";
+import recorder from 'librecorder.so';
-export class FileUtil {
+const DATETIME: DateTimeUtil = new DateTimeUtil();
+const TAG: string = 'FileUtil'
+
+export class FileUtil {
private static PhotoPicker: photoAccessHelper.PhotoViewPicker | undefined = undefined;
public static getPhotoViewPicker(): photoAccessHelper.PhotoViewPicker {
@@ -26,15 +36,98 @@ export class FileUtil {
}
static async selectFileFromLocal(): Promise {
- let result = await FileUtil.getPhotoViewPicker().select({
- MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
- maxSelectNumber: 1
- })
- let selectFilePath = result.photoUris[0];
- console.log('selectFileFromLocal-->2' + selectFilePath)
- if (!selectFilePath) {
- return undefined
+ try {
+ let result = await FileUtil.getPhotoViewPicker().select({
+ MIMEType: photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE,
+ maxSelectNumber: 1
+ })
+ let selectFilePath = result.photoUris[0];
+ console.log('selectFileFromLocal-->2' + selectFilePath)
+ if (!selectFilePath) {
+ return undefined
+ }
+ return selectFilePath;
+ } catch (e) {
+ return undefined;
+ }
+ }
+}
+
+@CustomDialog
+export struct SaveFileDialog {
+ controller?: CustomDialogController;
+ @Consume pageInfos: NavPathStack = new NavPathStack();
+ private cameraData: CameraDataModel = new CameraDataModel();
+
+ build() {
+ Column() {
+ Column() {
+ Row() {
+ Text($r('app.string.saveButtonTitle'))
+ .width('100%')
+ .textAlign(TextAlign.Center)
+ .fontWeight(FontWeight.Bold)
+ .fontSize($r('app.float.save_dialog_title_fontsize'))
+ }.width('100%')
+
+ Text($r('app.string.saveButtonNote'))
+ .width($r("app.string.full_width"))
+ .fontSize($r("app.float.authorized_text_fontsize"))
+ .margin({ top: $r('app.float.authorized_text_fontsize'), bottom: $r('app.float.authorized_text_fontsize') })
+
+ Row() {
+ Button($r('app.string.saveButtonCancel'), { buttonStyle: ButtonStyleMode.TEXTUAL })
+ .onClick(() => {
+ this.controller?.close();
+ })
+ .width($r("app.string.authorized_button_width"))
+
+ Blank()
+
+ SaveButton({ text: SaveDescription.SAVE })
+ .onClick(async () => {
+ try {
+ const context = this.getUIContext().getHostContext();
+ let helper = photoAccessHelper.getPhotoAccessHelper(context);
+ let uri = await helper.createAsset(photoAccessHelper.PhotoType.VIDEO, 'mp4', {
+ title: `AVCodecVideo_${DATETIME.getDate()}_${DATETIME.getTime()}`
+ });
+ let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
+ this.cameraData.outputFd = file.fd;
+ if (this.cameraData.outputFd !== null) {
+ recorder.initNative(this.cameraData.outputFd, this.cameraData.videoCodecMime,
+ this.cameraData.cameraWidth,
+ this.cameraData.cameraHeight, this.cameraData.frameRate, this.cameraData.isHDRVivid,
+ this.cameraData.bitRate).then((data) => {
+ if (data.surfaceId !== null) {
+ this.cameraData.surfaceId = data.surfaceId;
+ this.pageInfos.pushPath({
+ name: CommonConstants.RECORDER_PAGE,
+ param: new Params(SCENE.RECORDER, '', this.cameraData)
+ })
+ this.controller?.close();
+ }
+ })
+ } else {
+ Logger.error(TAG, 'get outputFd failed!');
+ }
+ } catch (e) {
+ Logger.error(TAG, 'get outputFd failed!');
+ }
+ })
+ .width($r("app.string.authorized_button_width"))
+ .height($r('app.float.authorized_button_height'))
+ }
+ .justifyContent(FlexAlign.SpaceAround)
+ .alignItems(VerticalAlign.Bottom)
+ .width($r('app.string.full_width'))
+ }
+ .justifyContent(FlexAlign.SpaceBetween)
+ .padding($r('app.float.save_dialog_padding'))
}
- return selectFilePath;
+ .borderRadius($r('app.float.save_dialog_borderRadius'))
+ .backgroundColor(Color.White)
+ .shadow(ShadowStyle.OUTER_DEFAULT_SM)
+ .width($r('app.string.save_dialog_width'))
}
-}
\ No newline at end of file
+}
diff --git a/entry/src/main/ets/common/utils/RecorderUtil.ets b/entry/src/main/ets/common/utils/RecorderUtil.ets
index 719994004bfbf33aef8f3a2d2944962b422af87e..3e23da9fe3774d1f99bb138843389d28fd00df5c 100644
--- a/entry/src/main/ets/common/utils/RecorderUtil.ets
+++ b/entry/src/main/ets/common/utils/RecorderUtil.ets
@@ -238,18 +238,24 @@ export class RecorderUtil {
try {
// Stop the Session.
- this.videoSession!.stop();
+ this.videoSession!.stop().catch(() => {
+ });
// Close file fd.
- fileIo.close(this.cameraData.outputfd);
+ fileIo.close(this.cameraData.outputFd).catch(() => {
+ });
// Close camera input stream.
- this.cameraInput!.close();
+ this.cameraInput!.close().catch(() => {
+ });
// Release preview output stream.
- this.XComponentPreviewOutput!.release();
+ this.XComponentPreviewOutput!.release().catch(() => {
+ });
// Release the video output stream.
- this.encoderVideoOutput!.release();
+ this.encoderVideoOutput!.release().catch(() => {
+ });
Logger.info(TAG, 'encoderVideoOutput release');
// Release session.
- this.videoSession!.release();
+ this.videoSession!.release().catch(() => {
+ });
} catch (error) {
Logger.error(TAG, `releaseCamera catch error, code: ${error.code}, message: ${error.message}`);
return;
diff --git a/entry/src/main/ets/model/CameraDateModel.ets b/entry/src/main/ets/model/CameraDateModel.ets
index 6b7d62dfd4ce497cecf73b85580ed0b6c930e7b5..8b371718177317d80e32b311993dd3192ff3c8d1 100644
--- a/entry/src/main/ets/model/CameraDateModel.ets
+++ b/entry/src/main/ets/model/CameraDateModel.ets
@@ -21,7 +21,7 @@ export class CameraDataModel {
public cameraWidth: number = Const.DEFAULT_WIDTH;
public cameraHeight: number = Const.DEFAULT_HEIGHT;
public isHDRVivid: number = Const.DEFAULT_VALUE;
- public outputfd: number = -1;
+ public outputFd: number = -1;
public frameRate: number = Const.FRAMERATE_VIDEO_30FPS;
public previewProfile: camera.Profile = Const.DEFAULT_PROFILE;
public videoCodecMime: string | null = Const.MIME_VIDEO_AVC;
diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets
index 597926516fba7fbc501019d9289da88a8a3f1e9d..ff0a50adf13244012ff6dc04c3a9b02853b2e752 100644
--- a/entry/src/main/ets/pages/Index.ets
+++ b/entry/src/main/ets/pages/Index.ets
@@ -14,93 +14,33 @@
*/
import { CommonConstants, CommonConstants as Const } from '../common/CommonConstants';
-import { photoAccessHelper } from '@kit.MediaLibraryKit';
-import DateTimeUtil from '../common/utils/DateTimeUtils';
-import { fileIo } from '@kit.CoreFileKit';
-import { CameraDataModel } from '../model/CameraDateModel';
-import Logger from '../common/utils/Logger';
-import recorder from 'librecorder.so';
import { Params, SCENE } from '../common/utils/RouterUtil';
import { PermissionUtil } from '../common/utils/PermissionUtil';
import { PermissionRequestResult } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
-import { FileUtil } from '../common/utils/FileUtil';
+import { FileUtil, SaveFileDialog } from '../common/utils/FileUtil';
+import Logger from '../common/utils/Logger';
const TAG: string = Const.INDEX_TAG;
-const DATETIME: DateTimeUtil = new DateTimeUtil();
@Entry
@Component
struct Index {
@Provide pageInfos: NavPathStack = new NavPathStack();
@State isShow: boolean = false;
- private cameraData: CameraDataModel = new CameraDataModel();
-
- @Builder
- Authorized() {
- Column() {
- Text($r('app.string.saveButtonNote'))
- .width($r("app.string.full_width"))
- .fontSize($r("app.float.authorized_text_fontsize"))
- .margin({ bottom: $r("app.float.authorized_text_margin") })
-
- Row() {
- Button($r('app.string.saveButtonCancel'))
- .onClick(() => {
- this.isShow = false;
- })
- .width($r("app.string.authorized_button_width"))
- Blank()
+ dialogController: CustomDialogController | null = new CustomDialogController({
+ builder: SaveFileDialog(),
+ customStyle: true,
+ })
- SaveButton({ text: SaveDescription.SAVE })
- .onClick(async () => {
- const context = this.getUIContext().getHostContext();
- let helper = photoAccessHelper.getPhotoAccessHelper(context);
- let uri = await helper.createAsset(photoAccessHelper.PhotoType.VIDEO, 'mp4', {
- title: `AVCodecVideo_${DATETIME.getDate()}_${DATETIME.getTime()}`
- });
- let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
- this.cameraData.outputfd = file.fd;
- if (this.cameraData.outputfd !== null) {
- recorder.initNative(this.cameraData.outputfd, this.cameraData.videoCodecMime, this.cameraData.cameraWidth,
- this.cameraData.cameraHeight, this.cameraData.frameRate, this.cameraData.isHDRVivid,
- this.cameraData.bitRate).then((data) => {
- if (data.surfaceId !== null) {
- this.cameraData.surfaceId = data.surfaceId;
- this.pageInfos.pushPath({
- name: CommonConstants.RECORDER_PAGE,
- param: new Params(SCENE.RECORDER, '', this.cameraData)
- })
- this.isShow = false;
- }
- })
- } else {
- Logger.error(TAG, 'get outputfd failed!');
- }
- })
- .width($r("app.string.authorized_button_width"))
- .height($r('app.float.authorized_button_height'))
- }
- .justifyContent(FlexAlign.SpaceAround)
- .alignItems(VerticalAlign.Bottom)
- .margin({ bottom: $r('app.float.authorized_row_margin') })
- .width($r('app.string.full_width'))
- .height($r('app.float.authorized_row_height'))
- }
- .justifyContent(FlexAlign.End)
- .padding({ left: $r('app.float.authorized_column_padding'), right: $r('app.float.authorized_column_padding') })
- .width($r('app.string.full_width'))
- .height($r('app.string.full_height'))
- }
build() {
Navigation(this.pageInfos) {
- Column() {
+ Column({ space: $r("app.float.column_space") }) {
Button($r("app.string.local_scene"), { stateEffect: true, type: ButtonType.Capsule })
.width($r('app.string.button_width'))
.height($r("app.float.set_button_height"))
- .margin($r("app.float.set_button_margin_right"))
.onClick(async () => {
let selectedFilePath: string | undefined = undefined
try {
@@ -119,23 +59,15 @@ struct Index {
Button($r("app.string.network_video_scene"), { stateEffect: true, type: ButtonType.Capsule })
.width($r('app.string.button_width'))
.height($r("app.float.set_button_height"))
- .margin($r("app.float.set_button_margin_right"))
.onClick(() => {
this.pageInfos.pushPath({
- name: CommonConstants.NETWORKVIDEO_PAGE
+ name: CommonConstants.NETWORK_VIDEO_PAGE
});
})
Button($r("app.string.recorder_scene"), { stateEffect: true, type: ButtonType.Capsule })
.width($r('app.string.button_width'))
.height($r("app.float.set_button_height"))
- .margin($r("app.float.set_button_margin_right"))
- .bindSheet($$this.isShow, this.Authorized, {
- height: $r("app.float.bindSheet_height"),
- title: {
- title: $r('app.string.saveButtonTitle')
- }
- })
.onClick(async () => {
let result: PermissionRequestResult =
await PermissionUtil.getInstance()
@@ -144,14 +76,20 @@ struct Index {
promptAction.openToast({
message: $r('app.string.open_camera_and_microphone_permissions'),
duration: 3000,
+ }).catch(() => {
})
return;
}
- this.isShow = true;
-
+ if (this.dialogController != null) {
+ this.dialogController.open();
+ }
})
}
- .padding({ top: $r("app.float.set_button_margin_right") })
+ .layoutWeight(1)
+ .width($r('app.string.full_width'))
+ .height($r('app.string.full_height'))
+ .justifyContent(FlexAlign.End)
+ .padding({ bottom: $r('app.float.set_button_margin_right') })
}
.title($r('app.string.EntryAbility_label'))
.backgroundColor($r("app.string.background_color"))
diff --git a/entry/src/main/ets/pages/NetworkVideo.ets b/entry/src/main/ets/pages/NetworkVideo.ets
index 3d228bfa7fd8594741a97eee9c48df047145149a..a41ac5e595dc42506c3ba412c9a5ede133e64cb7 100644
--- a/entry/src/main/ets/pages/NetworkVideo.ets
+++ b/entry/src/main/ets/pages/NetworkVideo.ets
@@ -21,7 +21,7 @@ import { Params, SCENE } from '../common/utils/RouterUtil';
const TAG: string = Const.PLAYER_PAGE;
@Builder
-export function PlayerBuilder(name: string, param: Object) {
+export function PlayerBuilder() {
NetworkVideo()
}
diff --git a/entry/src/main/ets/pages/Player.ets b/entry/src/main/ets/pages/PlayerSync.ets
similarity index 79%
rename from entry/src/main/ets/pages/Player.ets
rename to entry/src/main/ets/pages/PlayerSync.ets
index dd8570f67aba066d0ca98c6477697afc2c6945db..7a64ec10b247d97784d93255d77ec830e9282f6e 100644
--- a/entry/src/main/ets/pages/Player.ets
+++ b/entry/src/main/ets/pages/PlayerSync.ets
@@ -23,14 +23,21 @@ import { Params, SCENE } from '../common/utils/RouterUtil';
const TAG: string = Const.PLAYER_PAGE;
@Builder
-export function PlayerBuilder(name: string, param: Object) {
- Player()
+export function PlayerSyncBuilder() {
+ PlayerSync()
+}
+
+enum PlayStatus {
+ Stopped = 0,
+ Playing = 1,
+ Buffering = 2
}
@Component
-struct Player {
+struct PlayerSync {
@Consume pageInfos: NavPathStack;
@State buttonEnabled: boolean = true;
+ @State playStatus: PlayStatus = PlayStatus.Stopped;
@State isShow: boolean = false;
private selectFilePath: string | null = null;
private heightPx: string = '';
@@ -64,9 +71,16 @@ struct Player {
playNetworkVideo() {
this.buttonEnabled = false;
- player.playNative(-1, -1, 0, false, this.selectFilePath, () => {
+ player.playNative(-1, -1, 0, false, this.selectFilePath, (data) => {
hilog.info(0x0000, TAG, 'player JSCallback');
- this.buttonEnabled = true;
+ if (data == PlayStatus.Playing) {
+ this.playStatus = PlayStatus.Playing;
+ } else if (data == PlayStatus.Buffering) {
+ this.playStatus = PlayStatus.Buffering;
+ } else if (data === PlayStatus.Stopped) {
+ this.buttonEnabled = true;
+ this.playStatus = PlayStatus.Stopped;
+ }
});
}
@@ -80,12 +94,16 @@ struct Player {
if (inputFileState.size <= 0) {
hilog.error(0x0000, TAG, 'player inputFile size is 0');
}
- this.buttonEnabled = false;
- player.playNative(inputFile.fd, Const.DEFAULT_VALUE, inputFileState.size, true, '', () => {
- console.log('selectFileFromLocal-->7' + JSON.stringify(this.selectFilePath));
- hilog.info(0x0000, TAG, 'player JSCallback');
- this.buttonEnabled = true;
- fileIo.close(inputFile.fd);
+ player.playNative(inputFile.fd, Const.DEFAULT_VALUE, inputFileState.size, true, '', (data) => {
+ if (data == PlayStatus.Playing) {
+ this.playStatus = PlayStatus.Playing;
+ } else if (data == PlayStatus.Buffering) {
+ this.playStatus = PlayStatus.Buffering;
+ } else if (data === PlayStatus.Stopped) {
+ this.playStatus = PlayStatus.Stopped;
+ fileIo.close(inputFile.fd).catch(() => {
+ });
+ }
});
} catch (error) {
hilog.error(0x0000, TAG, `play catch error, code: ${error.code}, message: ${error.message}`);
@@ -112,7 +130,8 @@ struct Player {
Column({ space: $r("app.float.column_space") }) {
Column() {
- Button(this.buttonEnabled ? $r('app.string.play') : $r('app.string.playing'))
+ Button(this.playStatus === PlayStatus.Stopped ? $r('app.string.play') :
+ this.playStatus === PlayStatus.Buffering ? "缓冲中" : $r('app.string.playing'))
.onClick(() => {
this.playByScene();
})
@@ -120,7 +139,7 @@ struct Player {
width: $r('app.string.full_width'),
height: $r("app.float.set_button_height")
})
- .enabled(this.buttonEnabled)
+ .enabled(this.playStatus === PlayStatus.Stopped)
}
.padding({
left: $r('app.float.set_button_margin_right'),
@@ -138,7 +157,7 @@ struct Player {
width: $r('app.string.full_width'),
height: $r("app.float.set_button_height")
})
- .enabled(!this.buttonEnabled)
+ .enabled(this.playStatus === PlayStatus.Playing)
}
.padding({
left: $r('app.float.set_button_margin_right'),
@@ -156,7 +175,7 @@ struct Player {
width: $r('app.string.full_width'),
height: $r("app.float.set_button_height")
})
- .enabled(!this.buttonEnabled)
+ .enabled(this.playStatus === PlayStatus.Playing)
}
.padding({
left: $r('app.float.set_button_margin_right'),
@@ -174,7 +193,7 @@ struct Player {
width: $r('app.string.full_width'),
height: $r("app.float.set_button_height")
})
- .enabled(!this.buttonEnabled)
+ .enabled(this.playStatus === PlayStatus.Playing)
}
.padding({
left: $r('app.float.set_button_margin_right'),
@@ -185,11 +204,11 @@ struct Player {
}
.layoutWeight(1)
.width($r('app.string.full_width'))
- .height($r('app.string.full_width'))
+ .height($r('app.string.full_height'))
.justifyContent(FlexAlign.End)
.padding({ bottom: $r('app.float.set_button_margin_right') })
}
- .height($r('app.string.full_width'))
+ .height($r('app.string.full_height'))
.backgroundColor($r("app.string.background_color"))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.onHidden(() => {
diff --git a/entry/src/main/ets/pages/Recorder.ets b/entry/src/main/ets/pages/Recorder.ets
index d8c344f9c8e88ab5752a66b27f9b3ee347bdfcd7..9b2d88bbe88ad12e41c9b9057653c8f1b50eb233 100644
--- a/entry/src/main/ets/pages/Recorder.ets
+++ b/entry/src/main/ets/pages/Recorder.ets
@@ -27,7 +27,7 @@ import { FileUtil } from '../common/utils/FileUtil';
const TAG: string = Const.RECORDER_TAG;
@Builder
-export function RecorderBuilder(name: string, param: Object) {
+export function RecorderBuilder() {
Recorder()
}
@@ -43,9 +43,9 @@ struct Recorder {
private cameraWidth: number = Const.DEFAULT_WIDTH;
private cameraHeight: number = Const.DEFAULT_HEIGHT;
private XComponentController: XComponentController = new XComponentController();
- private display = display.getDefaultDisplaySync();
- private heightPx = (this.display.width * this.cameraWidth / this.cameraHeight) + Const.PX;
- private widthPx = this.display.width + Const.PX;
+ private display: display.Display | undefined = undefined;
+ private heightPx: string = '';
+ private widthPx: string = '';
private timer: number = Const.DEFAULT_VALUE;
private seconds: number = Const.DEFAULT_VALUE;
private isReleased: boolean = false;
@@ -74,6 +74,12 @@ struct Recorder {
}
aboutToAppear(): void {
+ try {
+ this.display = display.getDefaultDisplaySync();
+ this.heightPx = (this.display.width * this.cameraWidth / this.cameraHeight) + Const.PX;
+ this.widthPx = this.display.width + Const.PX;
+ } catch (error) {
+ }
let navParams: Params = this.pageInfos.getParamByName(CommonConstants.RECORDER_PAGE)[0] as Params;
this.cameraData = navParams.cameraData;
}
@@ -84,6 +90,7 @@ struct Recorder {
promptAction.openToast({
message: $r('app.string.stop_recording'),
duration: 3000,
+ }).catch(() => {
})
}
@@ -129,6 +136,7 @@ struct Recorder {
promptAction.openToast({
message: $r("app.string.start_recording"),
duration: 500,
+ }).catch(() => {
})
})
.width(this.widthPx)
@@ -152,10 +160,13 @@ struct Recorder {
if (currentFov < this.range[0]) {
currentFov = this.range[0];
}
- this.recorderUtil.videoSession.setZoomRatio(currentFov);
+ try {
+ this.recorderUtil.videoSession.setZoomRatio(currentFov);
+ } catch (error) {
+ }
}
})
- .onActionEnd((event: GestureEvent) => {
+ .onActionEnd(() => {
if (!this.recorderUtil) {
Logger.error(TAG, 'recorderUtil is undefined');
return;
@@ -164,7 +175,10 @@ struct Recorder {
Logger.error(TAG, 'recorderUtil videoSession is undefined');
return;
}
- this.fov = this.recorderUtil.videoSession.getZoomRatio();
+ try {
+ this.fov = this.recorderUtil.videoSession.getZoomRatio();
+ } catch (error) {
+ }
})
)
@@ -187,14 +201,14 @@ struct Recorder {
})
.enabled(this.buttonEnabled)
}
- .height(Const.FULL_SIZE)
- .width(Const.FULL_SIZE)
+ .width($r('app.string.full_width'))
+ .height($r('app.string.full_height'))
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.title($r("app.string.recorder_scene"))
.backgroundColor($r("app.string.background_color"))
.onHidden(() => {
- if(this.buttonEnabled){
+ if (this.buttonEnabled) {
this.stopRecord();
}
})
diff --git a/entry/src/main/resources/base/element/float.json b/entry/src/main/resources/base/element/float.json
index e1d654bc83c1a1f126552fa249b5943409295371..de586a206d0840e756a732e1a57ed84f5665fc3d 100644
--- a/entry/src/main/resources/base/element/float.json
+++ b/entry/src/main/resources/base/element/float.json
@@ -115,6 +115,18 @@
{
"name": "blank_height",
"value": "5vp"
+ },
+ {
+ "name": "save_dialog_borderRadius",
+ "value": "32"
+ },
+ {
+ "name": "save_dialog_padding",
+ "value": "22"
+ },
+ {
+ "name": "save_dialog_title_fontsize",
+ "value": "20"
}
]
}
\ No newline at end of file
diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json
index 964312a11e341f454766938bb2937e9cb3b403fa..4b36b4e87e65efd99f90bd0722076f288f4d8596 100644
--- a/entry/src/main/resources/base/element/string.json
+++ b/entry/src/main/resources/base/element/string.json
@@ -36,6 +36,10 @@
"name": "playing",
"value": "Playing"
},
+ {
+ "name": "buffing",
+ "value": "buffing"
+ },
{
"name": "play",
"value": "Play"
@@ -123,6 +127,10 @@
{
"name": "stop_recording",
"value": "Record finished, please select a video from the 'Local Video Audio-Visual Synchronization Scene Examples' to play."
+ },
+ {
+ "name": "save_dialog_width",
+ "value": "90%"
}
]
}
\ No newline at end of file
diff --git a/entry/src/main/resources/base/profile/router_map.json b/entry/src/main/resources/base/profile/router_map.json
index 3394e4e45c6e5e7fe5448d07b89c37936917d9d1..b7ed500779121366fe31cf5717f7efc83513b9ed 100644
--- a/entry/src/main/resources/base/profile/router_map.json
+++ b/entry/src/main/resources/base/profile/router_map.json
@@ -1,9 +1,9 @@
{
"routerMap": [
{
- "name": "Player",
- "pageSourceFile": "src/main/ets/pages/Player.ets",
- "buildFunction": "PlayerBuilder",
+ "name": "PlayerSync",
+ "pageSourceFile": "src/main/ets/pages/PlayerSync.ets",
+ "buildFunction": "PlayerSyncBuilder",
"data": {
"description": "this is Player Page"
}
diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json
index 69c2fd714c9536503d766fae682617f3e1995907..9b19567ad88c7dc8d60323e03a030216c9d2418b 100644
--- a/entry/src/main/resources/en_US/element/string.json
+++ b/entry/src/main/resources/en_US/element/string.json
@@ -32,6 +32,10 @@
"name": "playing",
"value": "Playing"
},
+ {
+ "name": "buffing",
+ "value": "buffing"
+ },
{
"name": "play",
"value": "Play"
@@ -123,6 +127,10 @@
{
"name": "stop_recording",
"value": "Record finished, please select a video from the 'Local Video Audio-Visual Synchronization Scene Examples' to play."
+ },
+ {
+ "name": "save_dialog_width",
+ "value": "90%"
}
]
}
\ No newline at end of file
diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json
index 5d351057e89364f7f0aae71d168f9c9e26df3531..40c8fe5fb1156364df768a699f4076778c1ee46a 100644
--- a/entry/src/main/resources/zh_CN/element/string.json
+++ b/entry/src/main/resources/zh_CN/element/string.json
@@ -32,6 +32,10 @@
"name": "playing",
"value": "播放中"
},
+ {
+ "name": "buffing",
+ "value": "缓冲中"
+ },
{
"name": "play",
"value": "播放"
@@ -123,6 +127,10 @@
{
"name": "stop_recording",
"value": "录制结束,请从”本地视频音画同步场景示例“中选择录制的视频播放"
+ },
+ {
+ "name": "save_dialog_width",
+ "value": "90%"
}
]
}
\ No newline at end of file
diff --git a/screenshots/device/ATVS_Index.en.png b/screenshots/device/ATVS_Index.en.png
deleted file mode 100644
index 8694eb9f3c2913824cd0e9178daef1585dd1e8d4..0000000000000000000000000000000000000000
Binary files a/screenshots/device/ATVS_Index.en.png and /dev/null differ
diff --git a/screenshots/device/ATVS_Index.png b/screenshots/device/ATVS_Index.png
deleted file mode 100644
index 594d4ef22e88e11c36009e9009732b471ba12696..0000000000000000000000000000000000000000
Binary files a/screenshots/device/ATVS_Index.png and /dev/null differ
diff --git a/screenshots/device/index.png b/screenshots/device/index.png
new file mode 100644
index 0000000000000000000000000000000000000000..224deaaa7e64660f6ea804531de087a1a0f5bf35
Binary files /dev/null and b/screenshots/device/index.png differ
diff --git a/screenshots/device/index_en.png b/screenshots/device/index_en.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c2e95abbfe9293039c1938a08fa3d4b4a078dc4
Binary files /dev/null and b/screenshots/device/index_en.png differ
diff --git a/screenshots/device/network.png b/screenshots/device/network.png
new file mode 100644
index 0000000000000000000000000000000000000000..de88cf28e6fb813b7ed6e1633cfcf684564673f1
Binary files /dev/null and b/screenshots/device/network.png differ
diff --git a/screenshots/device/network_en.png b/screenshots/device/network_en.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ad323e99d85b1e941a9560af0d620400395c9d2
Binary files /dev/null and b/screenshots/device/network_en.png differ
diff --git a/screenshots/device/play.en.png b/screenshots/device/play.en.png
deleted file mode 100644
index 2e721a0141c2a883d4d41438fb31561aac29e6d2..0000000000000000000000000000000000000000
Binary files a/screenshots/device/play.en.png and /dev/null differ
diff --git a/screenshots/device/play.png b/screenshots/device/play.png
deleted file mode 100644
index 0ca343d94e3d02a5f81b9023ac94965e5de45fc5..0000000000000000000000000000000000000000
Binary files a/screenshots/device/play.png and /dev/null differ
diff --git a/screenshots/device/record.png b/screenshots/device/record.png
new file mode 100644
index 0000000000000000000000000000000000000000..0a7691a7bead022e7610b2e551e3ea3d9a1259c0
Binary files /dev/null and b/screenshots/device/record.png differ
diff --git a/screenshots/device/record_en.png b/screenshots/device/record_en.png
new file mode 100644
index 0000000000000000000000000000000000000000..f0935ae2d1d4167ee93642b3804c2c20f4d678b6
Binary files /dev/null and b/screenshots/device/record_en.png differ
diff --git a/screenshots/device/sync.png b/screenshots/device/sync.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ca337631808728198723de11c9f133f9765c84b
Binary files /dev/null and b/screenshots/device/sync.png differ
diff --git a/screenshots/device/sync_en.png b/screenshots/device/sync_en.png
new file mode 100644
index 0000000000000000000000000000000000000000..89b311668bc39a6a58f75c14d461dc58f2b62622
Binary files /dev/null and b/screenshots/device/sync_en.png differ