From ea3aec66d777ede7305639ba47298abefd93888a Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:05:44 +0800 Subject: [PATCH 01/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4camera=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../integration_test/camera_test.dart | 489 ++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 ohos/automatic_camera_test/integration_test/camera_test.dart diff --git a/ohos/automatic_camera_test/integration_test/camera_test.dart b/ohos/automatic_camera_test/integration_test/camera_test.dart new file mode 100644 index 00000000..7870f8c6 --- /dev/null +++ b/ohos/automatic_camera_test/integration_test/camera_test.dart @@ -0,0 +1,489 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; +import 'dart:ui' as ui show Image; + +import 'package:camera_ohos/camera_ohos.dart'; +import 'package:automatic_camera_test/camera_controller.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:video_player/video_player.dart'; + +void main() { + late Directory testDir; + + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() async { + CameraPlatform.instance = OhosCamera(); + final Directory extDir = await getTemporaryDirectory(); + testDir = await Directory('${extDir.path}/integration_test').create(recursive: true); + }); + + tearDownAll(() async { + await testDir.delete(recursive: true); + }); + + final Map presetExpectedSizes = + { + ResolutionPreset.low: const Size(240, 320), + ResolutionPreset.medium: const Size(480, 720), + ResolutionPreset.high: const Size(720, 1280), + ResolutionPreset.veryHigh: const Size(1080, 1920), + ResolutionPreset.ultraHigh: const Size(2160, 3840), + // Don't bother checking for max here since it could be anything. + }; + + /// Verify that [actual] has dimensions that are at least as large as + /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns + /// whether the dimensions exactly match. + bool assertExpectedDimensions(Size expectedSize, Size actual) { + expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); + expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); + return actual.shortestSide == expectedSize.shortestSide && + actual.longestSide == expectedSize.longestSide; + } + + // This tests that the capture is no bigger than the preset, since we have + // automatic code to fall back to smaller sizes when we need to. Returns + // whether the image is exactly the desired resolution. + Future testCaptureImageResolution( + CameraController controller, ResolutionPreset preset) async { + final Size expectedSize = presetExpectedSizes[preset]!; + + // Take Picture + final XFile file = await controller.takePicture(); + + // Load picture + final File fileImage = File(file.path); + final Image image = (await decodeImageFromList(fileImage.readAsBytesSync())) as Image; + + // Verify image dimensions are as expected + expect(image, isNotNull); + return assertExpectedDimensions( + expectedSize, Size(image.height as double, image.width as double)); + } + + testWidgets( + 'Capture specific image resolutions', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + for (final CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (final MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); + await controller.initialize(); + final bool presetExactlySupported = + await testCaptureImageResolution(controller, preset.key); + assert(!(!previousPresetExactlySupported && presetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); + previousPresetExactlySupported = presetExactlySupported; + await controller.dispose(); + } + } + }, + // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. + skip: true, + ); + + // This tests that the capture is no bigger than the preset, since we have + // automatic code to fall back to smaller sizes when we need to. Returns + // whether the image is exactly the desired resolution. + Future testCaptureVideoResolution( + CameraController controller, ResolutionPreset preset) async { + final Size expectedSize = presetExpectedSizes[preset]!; + + // Take Video + await controller.startVideoRecording(); + sleep(const Duration(milliseconds: 300)); + final XFile file = await controller.stopVideoRecording(); + + // Load video metadata + final File videoFile = File(file.path); + final VideoPlayerController videoController = + VideoPlayerController.file(videoFile); + await videoController.initialize(); + final Size video = videoController.value.size; + + // Verify image dimensions are as expected + expect(video, isNotNull); + return assertExpectedDimensions( + expectedSize, Size(video.height, video.width)); + } + + testWidgets( + 'Capture specific video resolutions', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + for (final CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (final MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); + await controller.initialize(); + await controller.prepareForVideoRecording(); + final bool presetExactlySupported = + await testCaptureVideoResolution(controller, preset.key); + assert(!(!previousPresetExactlySupported && presetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); + previousPresetExactlySupported = presetExactlySupported; + await controller.dispose(); + } + } + }, + // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. + skip: true, + ); + + testWidgets('Pause and resume video recording', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.prepareForVideoRecording(); + + int startPause; + int timePaused = 0; + + await controller.startVideoRecording(); + final int recordingStart = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + + await controller.pauseVideoRecording(); + startPause = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + await controller.resumeVideoRecording(); + timePaused += DateTime.now().millisecondsSinceEpoch - startPause; + + sleep(const Duration(milliseconds: 500)); + + await controller.pauseVideoRecording(); + startPause = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + await controller.resumeVideoRecording(); + timePaused += DateTime.now().millisecondsSinceEpoch - startPause; + + sleep(const Duration(milliseconds: 500)); + + final XFile file = await controller.stopVideoRecording(); + + final int recordingTime = + DateTime.now().millisecondsSinceEpoch - recordingStart; + + final File videoFile = File(file.path); + final VideoPlayerController videoController = VideoPlayerController.file( + videoFile, + ); + await videoController.initialize(); + final int duration = videoController.value.duration.inMilliseconds; + await videoController.dispose(); + + print("====Pause and resume video recording-" + "${(duration < (recordingTime - timePaused)) == true ? "success" : "failed"}:"); + expect(duration, lessThan(recordingTime - timePaused)); + try { + controller.dispose(); + } on Exception catch (e) { + print(""); + } + }); + + testWidgets('Set description while recording', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.prepareForVideoRecording(); + + await controller.startVideoRecording(); + + // SDK < 26 will throw a platform error when trying to switch and keep the same camera + // we accept either outcome here, while the native unit tests check the outcome based on the current Android SDK + bool failed = false; + try { + await controller.setDescription(cameras[1]); + } catch (err) { + // expect(err, isA()); + // expect( + // (err as PlatformException).message, + // equals( + // 'Device does not support switching the camera while recording')); + failed = true; + } + + if (failed) { + // cameras did not switch + print("====Set description while recording-" + "${(controller.description==cameras[0]) == true ? "success" : "failed"}:"); + // expect(controller.description, cameras[0]); + } else { + // cameras switched + print("====Set description while recording-" + "${(controller.description==cameras[1]) == true ? "success" : "failed"}:"); + // expect(controller.description, cameras[1]); + } + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + }); + + testWidgets('Set description', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.setDescription(cameras[1]); + + print("====Set description-" + "${(controller.description==cameras[1]) == true ? "success" : "failed"}:"); + expect(controller.description, cameras[1]); + + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + }); + + testWidgets( + 'image streaming', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + bool isDetecting = false; + + await controller.startImageStream((CameraImageData image) { + if (isDetecting) { + return; + } + + isDetecting = true; + + expectLater(image, isNotNull).whenComplete(() => isDetecting = false); + }); + + print("====image streaming-" + "${controller.value.isStreamingImages == true ? "success" : "failed"}:"); + expect(controller.value.isStreamingImages, true); + + sleep(const Duration(milliseconds: 500)); + + await controller.stopImageStream(); + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + }, + ); + + testWidgets( + 'recording with image stream', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + bool isDetecting = false; + + await controller.startVideoRecording( + streamCallback: (CameraImageData image) { + if (isDetecting) { + return; + } + + isDetecting = true; + + expectLater(image, isNotNull); + }); + + var flag = controller.value.isStreamingImages == true; + expect(controller.value.isStreamingImages, true); + + await Future.delayed(const Duration(seconds: 2)); + + await controller.stopVideoRecording(); + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + print("====recording with image stream-" + "${(controller.value.isStreamingImages == false) && flag == true ? "success" : "failed"}:"); + expect(controller.value.isStreamingImages, false); + }, + ); + + testWidgets( + 'takePicture', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + await controller.initialize(); + + // Take Picture + final XFile file = await controller.takePicture(); + // Load picture + final File fileImage = File(file.path); + + final ui.Image image = await decodeImageFromList(fileImage.readAsBytesSync()); + + print("====takePicture-" + "${(image != null) == true ? "success" : "failed"}:"); + try { + controller.dispose(); + } on Exception catch(e) { + print(""); + } + }, + ); + + testWidgets( + 'previewerMode', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + + final Widget widget = controller.buildPreview(); + + print("====previewerMode - build previewer :" + "${(widget as Texture).textureId == controller.cameraId ? "success" : "failed"}:"); + + await controller.pausePreview(); + + print("====previewerMode - paused previewer: " + "${(controller.value.isPreviewPaused) ? "success" : "failed"}:"); + + await controller.resumePreview(); + print("====previewerMode - resume previewer: " + "${(!controller.value.isPreviewPaused) ? "success" : "failed"}:"); + + try { + controller.dispose(); + } on Exception catch(e) { + print(""); + } + }, + ); + + testWidgets( + 'ExposureMode', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.setExposureMode(ExposureMode.auto); + + print("====ExposureMode - set ExposureMode :" + "${controller.value.exposureMode == ExposureMode.auto ? "success" : "failed"}:"); + + final offset = await controller.setExposureOffset(1.0); + + print("set offset is ${offset}"); + print("====ExposureMode - set ExposureOffset : " + "${(offset == 2.0) ? "success" : "failed"}:"); + + try { + controller.dispose(); + } on Exception catch(e) { + print(""); + } + }, + ); +} -- Gitee From ad4c506756863e14ba834419e48229cd2d4326d7 Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:06:17 +0800 Subject: [PATCH 02/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4camera=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../lib/camera_controller.dart | 574 +++++++++ .../lib/camera_preview.dart | 87 ++ .../lib/fileselector/file_selector.dart | 33 + .../lib/fileselector/file_selector_api.dart | 142 +++ ohos/automatic_camera_test/lib/main.dart | 1087 +++++++++++++++++ 5 files changed, 1923 insertions(+) create mode 100644 ohos/automatic_camera_test/lib/camera_controller.dart create mode 100644 ohos/automatic_camera_test/lib/camera_preview.dart create mode 100644 ohos/automatic_camera_test/lib/fileselector/file_selector.dart create mode 100644 ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart create mode 100644 ohos/automatic_camera_test/lib/main.dart diff --git a/ohos/automatic_camera_test/lib/camera_controller.dart b/ohos/automatic_camera_test/lib/camera_controller.dart new file mode 100644 index 00000000..9ab06140 --- /dev/null +++ b/ohos/automatic_camera_test/lib/camera_controller.dart @@ -0,0 +1,574 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:collection'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// The state of a [CameraController]. +class CameraValue { + /// Creates a new camera controller state. + const CameraValue({ + required this.isInitialized, + this.previewSize, + required this.isRecordingVideo, + required this.isTakingPicture, + required this.isStreamingImages, + required this.isRecordingPaused, + required this.flashMode, + required this.exposureMode, + required this.focusMode, + required this.deviceOrientation, + required this.description, + this.lockedCaptureOrientation, + this.recordingOrientation, + this.isPreviewPaused = false, + this.previewPauseOrientation, + }); + + /// Creates a new camera controller state for an uninitialized controller. + const CameraValue.uninitialized(CameraDescription description) + : this( + isInitialized: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + isRecordingPaused: false, + flashMode: FlashMode.auto, + exposureMode: ExposureMode.auto, + focusMode: FocusMode.auto, + deviceOrientation: DeviceOrientation.portraitUp, + isPreviewPaused: false, + description: description, + ); + + /// True after [CameraController.initialize] has completed successfully. + final bool isInitialized; + + /// True when a picture capture request has been sent but as not yet returned. + final bool isTakingPicture; + + /// True when the camera is recording (not the same as previewing). + final bool isRecordingVideo; + + /// True when images from the camera are being streamed. + final bool isStreamingImages; + + /// True when video recording is paused. + final bool isRecordingPaused; + + /// True when the preview widget has been paused manually. + final bool isPreviewPaused; + + /// Set to the orientation the preview was paused in, if it is currently paused. + final DeviceOrientation? previewPauseOrientation; + + /// The size of the preview in pixels. + /// + /// Is `null` until [isInitialized] is `true`. + final Size? previewSize; + + /// The flash mode the camera is currently set to. + final FlashMode flashMode; + + /// The exposure mode the camera is currently set to. + final ExposureMode exposureMode; + + /// The focus mode the camera is currently set to. + final FocusMode focusMode; + + /// The current device UI orientation. + final DeviceOrientation deviceOrientation; + + /// The currently locked capture orientation. + final DeviceOrientation? lockedCaptureOrientation; + + /// Whether the capture orientation is currently locked. + bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; + + /// The orientation of the currently running video recording. + final DeviceOrientation? recordingOrientation; + + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + + /// Creates a modified copy of the object. + /// + /// Explicitly specified fields get the specified value, all other fields get + /// the same value of the current object. + CameraValue copyWith({ + bool? isInitialized, + bool? isRecordingVideo, + bool? isTakingPicture, + bool? isStreamingImages, + Size? previewSize, + bool? isRecordingPaused, + FlashMode? flashMode, + ExposureMode? exposureMode, + FocusMode? focusMode, + bool? exposurePointSupported, + bool? focusPointSupported, + DeviceOrientation? deviceOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, + bool? isPreviewPaused, + CameraDescription? description, + Optional? previewPauseOrientation, + }) { + return CameraValue( + isInitialized: isInitialized ?? this.isInitialized, + previewSize: previewSize ?? this.previewSize, + isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, + isTakingPicture: isTakingPicture ?? this.isTakingPicture, + isStreamingImages: isStreamingImages ?? this.isStreamingImages, + isRecordingPaused: isRecordingPaused ?? this.isRecordingPaused, + flashMode: flashMode ?? this.flashMode, + exposureMode: exposureMode ?? this.exposureMode, + focusMode: focusMode ?? this.focusMode, + deviceOrientation: deviceOrientation ?? this.deviceOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, + isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, + description: description ?? this.description, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, + ); + } + + @override + String toString() { + return '${objectRuntimeType(this, 'CameraValue')}(' + 'isRecordingVideo: $isRecordingVideo, ' + 'isInitialized: $isInitialized, ' + 'previewSize: $previewSize, ' + 'isStreamingImages: $isStreamingImages, ' + 'flashMode: $flashMode, ' + 'exposureMode: $exposureMode, ' + 'focusMode: $focusMode, ' + 'deviceOrientation: $deviceOrientation, ' + 'lockedCaptureOrientation: $lockedCaptureOrientation, ' + 'recordingOrientation: $recordingOrientation, ' + 'isPreviewPaused: $isPreviewPaused, ' + 'previewPausedOrientation: $previewPauseOrientation)'; + } +} + +/// Controls a device camera. +/// +/// This is a stripped-down version of the app-facing controller to serve as a +/// utility for the example and integration tests. It wraps only the calls that +/// have state associated with them, to consolidate tracking of camera state +/// outside of the overall example code. +class CameraController extends ValueNotifier { + /// Creates a new camera controller in an uninitialized state. + CameraController( + CameraDescription cameraDescription, + this.resolutionPreset, { + this.enableAudio = true, + this.imageFormatGroup, + }) : super(CameraValue.uninitialized(cameraDescription)); + + /// The properties of the camera device controlled by this controller. + CameraDescription get description => value.description; + + /// The resolution this controller is targeting. + /// + /// This resolution preset is not guaranteed to be available on the device, + /// if unavailable a lower resolution will be used. + /// + /// See also: [ResolutionPreset]. + final ResolutionPreset resolutionPreset; + + /// Whether to include audio when recording a video. + final bool enableAudio; + + /// The [ImageFormatGroup] describes the output of the raw image format. + /// + /// When null the imageFormat will fallback to the platforms default. + final ImageFormatGroup? imageFormatGroup; + + late int _cameraId; + + bool _isDisposed = false; + StreamSubscription? _imageStreamSubscription; + FutureOr? _initCalled; + StreamSubscription? + _deviceOrientationSubscription; + + /// The camera identifier with which the controller is associated. + int get cameraId => _cameraId; + + /// Initializes the camera on the device. + Future initialize() => _initializeWithDescription(description); + + Future _initializeWithDescription(CameraDescription description) async { + final Completer initializeCompleter = + Completer(); + + _deviceOrientationSubscription = CameraPlatform.instance + .onDeviceOrientationChanged() + .listen((DeviceOrientationChangedEvent event) { + value = value.copyWith( + deviceOrientation: event.orientation, + ); + }); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); + + unawaited(CameraPlatform.instance + .onCameraInitialized(_cameraId) + .first + .then((CameraInitializedEvent event) { + initializeCompleter.complete(event); + })); + + await CameraPlatform.instance.initializeCamera( + _cameraId, + imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, + ); + + value = value.copyWith( + isInitialized: true, + description: description, + previewSize: await initializeCompleter.future + .then((CameraInitializedEvent event) => Size( + event.previewWidth, + event.previewHeight, + )), + exposureMode: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.exposureMode), + focusMode: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.focusMode), + exposurePointSupported: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.exposurePointSupported), + focusPointSupported: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.focusPointSupported), + ); + + _initCalled = true; + } + + /// Prepare the capture session for video recording. + Future prepareForVideoRecording() async { + await CameraPlatform.instance.prepareForVideoRecording(); + } + + /// Pauses the current camera preview + Future pausePreview() async { + await CameraPlatform.instance.pausePreview(_cameraId); + value = value.copyWith( + isPreviewPaused: true, + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); + } + + /// Resumes the current camera preview + Future resumePreview() async { + await CameraPlatform.instance.resumePreview(_cameraId); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); + } + + /// Sets the description of the camera. + Future setDescription(CameraDescription description) async { + if (value.isRecordingVideo) { + await CameraPlatform.instance.setDescriptionWhileRecording(description); + value = value.copyWith(description: description); + } else { + await _initializeWithDescription(description); + } + } + + /// Captures an image and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture fails. + Future takePicture() async { + value = value.copyWith(isTakingPicture: true); + final XFile file = await CameraPlatform.instance.takePicture(_cameraId); + value = value.copyWith(isTakingPicture: false); + return file; + } + + /// Start streaming images from platform camera. + Future startImageStream( + Function(CameraImageData image) onAvailable) async { + _imageStreamSubscription = CameraPlatform.instance + .onStreamedFrameAvailable(_cameraId) + .listen((CameraImageData imageData) { + onAvailable(imageData); + }); + value = value.copyWith(isStreamingImages: true); + } + + /// Stop streaming images from platform camera. + Future stopImageStream() async { + value = value.copyWith(isStreamingImages: false); + await _imageStreamSubscription?.cancel(); + _imageStreamSubscription = null; + } + + /// Start a video recording. + /// + /// The video is returned as a [XFile] after calling [stopVideoRecording]. + /// Throws a [CameraException] if the capture fails. + Future startVideoRecording( + {Function(CameraImageData image)? streamCallback}) async { + await CameraPlatform.instance.startVideoCapturing( + VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); + value = value.copyWith( + isRecordingVideo: true, + isRecordingPaused: false, + isStreamingImages: streamCallback != null, + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); + } + + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { + if (value.isStreamingImages) { + await stopImageStream(); + } + + final XFile file = + await CameraPlatform.instance.stopVideoRecording(_cameraId); + value = value.copyWith( + isRecordingVideo: false, + isRecordingPaused: false, + recordingOrientation: const Optional.absent(), + ); + return file; + } + + /// Pause video recording. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future pauseVideoRecording() async { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: true); + } + + /// Resume video recording after pausing. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future resumeVideoRecording() async { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: false); + } + + /// Returns a widget showing a live camera preview. + Widget buildPreview() { + return CameraPlatform.instance.buildPreview(_cameraId); + } + + /// Sets the flash mode for taking pictures. + Future setFlashMode(FlashMode mode) async { + await CameraPlatform.instance.setFlashMode(_cameraId, mode); + value = value.copyWith(flashMode: mode); + } + + /// Sets the exposure mode for taking pictures. + Future setExposureMode(ExposureMode mode) async { + await CameraPlatform.instance.setExposureMode(_cameraId, mode); + value = value.copyWith(exposureMode: mode); + } + + /// Sets the exposure offset for the selected camera. + Future setExposureOffset(double offset) async { + // Check if offset is in range + final List range = await Future.wait(>[ + CameraPlatform.instance.getMinExposureOffset(_cameraId), + CameraPlatform.instance.getMaxExposureOffset(_cameraId) + ]); + + // Round to the closest step if needed + final double stepSize = + await CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); + if (stepSize > 0) { + final double inv = 1.0 / stepSize; + double roundedOffset = (offset * inv).roundToDouble() / inv; + if (roundedOffset > range[1]) { + roundedOffset = (offset * inv).floorToDouble() / inv; + } else if (roundedOffset < range[0]) { + roundedOffset = (offset * inv).ceilToDouble() / inv; + } + offset = roundedOffset; + } + + return CameraPlatform.instance.setExposureOffset(_cameraId, offset); + } + + /// Locks the capture orientation. + /// + /// If [orientation] is omitted, the current device orientation is used. + Future lockCaptureOrientation() async { + await CameraPlatform.instance + .lockCaptureOrientation(_cameraId, value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: + Optional.of(value.deviceOrientation)); + } + + /// Unlocks the capture orientation. + Future unlockCaptureOrientation() async { + await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); + } + + /// Sets the focus mode for taking pictures. + Future setFocusMode(FocusMode mode) async { + await CameraPlatform.instance.setFocusMode(_cameraId, mode); + value = value.copyWith(focusMode: mode); + } + + /// Releases the resources of this camera. + @override + Future dispose() async { + if (_isDisposed) { + return; + } + _isDisposed = true; + await _deviceOrientationSubscription?.cancel(); + super.dispose(); + if (_initCalled != null) { + await _initCalled; + await CameraPlatform.instance.dispose(_cameraId); + } + } + + @override + void removeListener(VoidCallback listener) { + // Prevent ValueListenableBuilder in CameraPreview widget from causing an + // exception to be thrown by attempting to remove its own listener after + // the controller has already been disposed. + if (!_isDisposed) { + super.removeListener(listener); + } + } +} + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/ohos/automatic_camera_test/lib/camera_preview.dart b/ohos/automatic_camera_test/lib/camera_preview.dart new file mode 100644 index 00000000..629409cf --- /dev/null +++ b/ohos/automatic_camera_test/lib/camera_preview.dart @@ -0,0 +1,87 @@ + // Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:math' as math; + +import 'camera_controller.dart'; + +/// A widget showing a live camera preview. +class CameraPreview extends StatelessWidget { + /// Creates a preview widget for the given camera controller. + const CameraPreview(this.controller, {super.key, this.child}); + + /// The controller for the camera that the preview is shown for. + final CameraController controller; + + /// A widget to overlay on top of the camera preview + final Widget? child; + + @override + Widget build(BuildContext context) { + + return controller.value.isInitialized + ? ValueListenableBuilder( + valueListenable: controller, + builder: (BuildContext context, Object? value, Widget? child) { + final double cameraAspectRatio = + controller.value.previewSize!.width / + controller.value.previewSize!.height; + return AspectRatio( + aspectRatio: _isLandscape() + ? cameraAspectRatio + : (1 / cameraAspectRatio), + child: Stack( + fit: StackFit.expand, + children: [ + _wrapInRotatedBox(child: controller.buildPreview()), + child ?? Container(), + ], + ), + ); + }, + child: child, + ) + : Container(); + } + + Widget _wrapInRotatedBox({required Widget child}) { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.ohos) { + return child; + } + + return RotatedBox( + quarterTurns: _getQuarterTurns(), + child: child, + ); + } + + bool _isLandscape() { + return [ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight + ].contains(_getApplicableOrientation()); + } + + int _getQuarterTurns() { + final Map turns = { + DeviceOrientation.portraitUp: 0, + DeviceOrientation.landscapeRight: 1, + DeviceOrientation.portraitDown: 2, + DeviceOrientation.landscapeLeft: 3, + }; + return turns[_getApplicableOrientation()]!; + } + + DeviceOrientation _getApplicableOrientation() { + return controller.value.isRecordingVideo + ? controller.value.recordingOrientation! + : (controller.value.previewPauseOrientation ?? + controller.value.lockedCaptureOrientation ?? + controller.value.deviceOrientation); + } +} diff --git a/ohos/automatic_camera_test/lib/fileselector/file_selector.dart b/ohos/automatic_camera_test/lib/fileselector/file_selector.dart new file mode 100644 index 00000000..693746d5 --- /dev/null +++ b/ohos/automatic_camera_test/lib/fileselector/file_selector.dart @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'file_selector_api.dart'; + +class FileSelector { + FileSelector({ FileSelectorApi? api}) + : _api = api ?? FileSelectorApi(); + + final FileSelectorApi _api; + + /// Registers this class as the implementation of the file_selector platform interface. + /// + @override + Future openFileByPath(String path) async { + final int? fd = await _api.openFileByPath(path); + print("openfile#"); + print(fd); + return fd; + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart b/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart new file mode 100644 index 00000000..8ff6df7c --- /dev/null +++ b/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart @@ -0,0 +1,142 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'dart:async'; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class FileResponse { + FileResponse({ + required this.path, + this.name, + required this.fd, + }); + + String path; + + String? name; + + int fd; + + Object encode() { + return [ + path, + name, + fd, + ]; + } + + static FileResponse decode(Object result) { + result as List; + return FileResponse( + path: result[0]! as String, + name: result[1] as String?, + fd: result[2]! as int, + ); + } +} + +class FileTypes { + FileTypes({ + required this.mimeTypes, + required this.extensions, + }); + + List mimeTypes; + + List extensions; + + Object encode() { + return [ + mimeTypes, + extensions, + ]; + } + + static FileTypes decode(Object result) { + result as List; + return FileTypes( + mimeTypes: (result[0] as List?)!.cast(), + extensions: (result[1] as List?)!.cast(), + ); + } +} + +class _FileSelectorApiCodec extends StandardMessageCodec { + const _FileSelectorApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is FileResponse) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is FileTypes) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return FileResponse.decode(readValue(buffer)!); + case 129: + return FileTypes.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// An API to call to native code to select files or directories. +class FileSelectorApi { + /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + FileSelectorApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _FileSelectorApiCodec(); + + /// Opens a file dialog for loading files and returns a file path. + /// + /// Returns `null` if user cancels the operation. + Future openFileByPath(String path) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.FileSelectorApi.openFileByPath', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([path]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as int?); + } + } + +} diff --git a/ohos/automatic_camera_test/lib/main.dart b/ohos/automatic_camera_test/lib/main.dart new file mode 100644 index 00000000..8d4b760a --- /dev/null +++ b/ohos/automatic_camera_test/lib/main.dart @@ -0,0 +1,1087 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:camera_ohos/camera_ohos.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:video_player/video_player.dart'; + +import 'camera_controller.dart'; +import 'camera_preview.dart'; +import 'fileselector/file_selector.dart'; + +/// Camera example home widget. +class CameraExampleHome extends StatefulWidget { + /// Default Constructor + const CameraExampleHome({super.key}); + + @override + State createState() { + return _CameraExampleHomeState(); + } +} + +/// Returns a suitable camera icon for [direction]. +IconData getCameraLensIcon(CameraLensDirection direction) { + switch (direction) { + case CameraLensDirection.back: + return Icons.camera_rear; + case CameraLensDirection.front: + return Icons.camera_front; + case CameraLensDirection.external: + return Icons.camera; + } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; +} + +void _logError(String code, String? message) { + // ignore: avoid_print + print('Error: $code${message == null ? '' : '\nError Message: $message'}'); +} + +class _CameraExampleHomeState extends State + with WidgetsBindingObserver, TickerProviderStateMixin { + CameraController? controller; + XFile? imageFile; + XFile? videoFile; + VideoPlayerController? videoController; + VoidCallback? videoPlayerListener; + bool enableAudio = true; + double _minAvailableExposureOffset = 0.0; + double _maxAvailableExposureOffset = 0.0; + double _currentExposureOffset = 0.0; + late AnimationController _flashModeControlRowAnimationController; + late Animation _flashModeControlRowAnimation; + late AnimationController _exposureModeControlRowAnimationController; + late Animation _exposureModeControlRowAnimation; + late AnimationController _focusModeControlRowAnimationController; + late Animation _focusModeControlRowAnimation; + double _minAvailableZoom = 1.0; + double _maxAvailableZoom = 1.0; + double _currentScale = 1.0; + double _baseScale = 1.0; + + // Counting pointers (number of user fingers on screen) + int _pointers = 0; + + @override + void initState() { + super.initState(); + + _ambiguate(WidgetsBinding.instance)?.addObserver(this); + + _flashModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _flashModeControlRowAnimation = CurvedAnimation( + parent: _flashModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _exposureModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _exposureModeControlRowAnimation = CurvedAnimation( + parent: _exposureModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _focusModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _focusModeControlRowAnimation = CurvedAnimation( + parent: _focusModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + } + + @override + void dispose() { + _ambiguate(WidgetsBinding.instance)?.removeObserver(this); + _flashModeControlRowAnimationController.dispose(); + _exposureModeControlRowAnimationController.dispose(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final CameraController? cameraController = controller; + + // App state changed before we got the chance to initialize. + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + + if (state == AppLifecycleState.inactive) { + cameraController.dispose(); + } else if (state == AppLifecycleState.resumed) { + _initializeCameraController(cameraController.description); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Camera example'), + ), + body: Column( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.black, + border: Border.all( + color: + controller != null && controller!.value.isRecordingVideo + ? Colors.redAccent + : Colors.grey, + width: 3.0, + ), + ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Center( + child: _cameraPreviewWidget(), + ), + ), + ), + ), + _captureControlRowWidget(), + _modeControlRowWidget(), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + _cameraTogglesRowWidget(), + _thumbnailWidget(), + ], + ), + ), + ], + ), + ); + } + + /// Display the preview from the camera (or a message if the preview is not available). + Widget _cameraPreviewWidget() { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + return const Text( + 'Tap a camera', + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.w900, + ), + ); + } else { + return Listener( + onPointerDown: (_) => _pointers++, + onPointerUp: (_) => _pointers--, + child: CameraPreview( + controller!, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onTapDown: (TapDownDetails details) => + onViewFinderTap(details, constraints), + ); + }), + ), + ); + } + } + + void _handleScaleStart(ScaleStartDetails details) { + _baseScale = _currentScale; + } + + Future _handleScaleUpdate(ScaleUpdateDetails details) async { + // When there are not exactly two fingers on screen don't scale + if (controller == null || _pointers != 2) { + return; + } + + _currentScale = (_baseScale * details.scale) + .clamp(_minAvailableZoom, _maxAvailableZoom); + + await CameraPlatform.instance + .setZoomLevel(controller!.cameraId, _currentScale); + } + + /// Display the thumbnail of the captured image or video. + Widget _thumbnailWidget() { + final VideoPlayerController? localVideoController = videoController; + + return Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (localVideoController == null && imageFile == null) + Container() + else + SizedBox( + width: 64.0, + height: 64.0, + child: (localVideoController == null) + ? ( + // The captured image on the web contains a network-accessible URL + // pointing to a location within the browser. It may be displayed + // either with Image.network or Image.memory after loading the image + // bytes to memory. + kIsWeb + ? Image.network(imageFile!.path) + : Image.file(File(imageFile!.path))) + : Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.pink)), + child: Center( + child: AspectRatio( + aspectRatio: + localVideoController.value.aspectRatio, + child: VideoPlayer(localVideoController)), + ), + ), + ), + ], + ), + ), + ); + } + + /// Display a bar with buttons to change the flash and exposure modes + Widget _modeControlRowWidget() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.flash_on), + color: Colors.blue, + onPressed: controller != null ? onFlashModeButtonPressed : null, + ), + // The exposure and focus mode are currently not supported on the web. + ...!kIsWeb + ? [ + IconButton( + icon: const Icon(Icons.exposure), + color: Colors.blue, + onPressed: controller != null + ? onExposureModeButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: + controller != null ? onFocusModeButtonPressed : null, + ) + ] + : [], + IconButton( + icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), + color: Colors.blue, + onPressed: controller != null ? onAudioModeButtonPressed : null, + ), + IconButton( + icon: Icon(controller?.value.isCaptureOrientationLocked ?? false + ? Icons.screen_lock_rotation + : Icons.screen_rotation), + color: Colors.blue, + onPressed: controller != null + ? onCaptureOrientationLockButtonPressed + : null, + ), + ], + ), + _flashModeControlRowWidget(), + _exposureModeControlRowWidget(), + _focusModeControlRowWidget(), + ], + ); + } + + Widget _flashModeControlRowWidget() { + return SizeTransition( + sizeFactor: _flashModeControlRowAnimation, + child: ClipRect( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.flash_off), + color: controller?.value.flashMode == FlashMode.off + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.off) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_auto), + color: controller?.value.flashMode == FlashMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.auto) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_on), + color: controller?.value.flashMode == FlashMode.always + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.always) + : null, + ), + IconButton( + icon: const Icon(Icons.highlight), + color: controller?.value.flashMode == FlashMode.torch + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.torch) + : null, + ), + ], + ), + ), + ); + } + + Widget _exposureModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + foregroundColor: controller?.value.exposureMode == ExposureMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + foregroundColor: controller?.value.exposureMode == ExposureMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _exposureModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + const Center( + child: Text('Exposure Mode'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + style: styleAuto, + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.auto) + : null, + onLongPress: () { + if (controller != null) { + CameraPlatform.instance + .setExposurePoint(controller!.cameraId, null); + showInSnackBar('Resetting exposure point'); + } + }, + child: const Text('AUTO'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.locked) + : null, + child: const Text('LOCKED'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => controller!.setExposureOffset(0.0) + : null, + child: const Text('RESET OFFSET'), + ), + ], + ), + const Center( + child: Text('Exposure Offset'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text(_minAvailableExposureOffset.toString()), + Slider( + value: _currentExposureOffset, + min: _minAvailableExposureOffset, + max: _maxAvailableExposureOffset, + label: _currentExposureOffset.toString(), + onChanged: _minAvailableExposureOffset == + _maxAvailableExposureOffset + ? null + : setExposureOffset, + ), + Text(_maxAvailableExposureOffset.toString()), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _focusModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + foregroundColor: controller?.value.focusMode == FocusMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + foregroundColor: controller?.value.focusMode == FocusMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _focusModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + const Center( + child: Text('Focus Mode'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + style: styleAuto, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.auto) + : null, + onLongPress: () { + if (controller != null) { + CameraPlatform.instance + .setFocusPoint(controller!.cameraId, null); + } + showInSnackBar('Resetting focus point'); + }, + child: const Text('AUTO'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.locked) + : null, + child: const Text('LOCKED'), + ), + ], + ), + ], + ), + ), + ), + ); + } + + /// Display the control bar with buttons to take pictures and record videos. + Widget _captureControlRowWidget() { + final CameraController? cameraController = controller; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.camera_alt), + color: Colors.blue, + onPressed: cameraController != null && + cameraController.value.isInitialized && + !cameraController.value.isRecordingVideo + ? onTakePictureButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.videocam), + color: Colors.blue, + onPressed: cameraController != null && + cameraController.value.isInitialized && + !cameraController.value.isRecordingVideo + ? onVideoRecordButtonPressed + : null, + ), + IconButton( + icon: cameraController != null && + (!cameraController.value.isRecordingVideo || + cameraController.value.isRecordingPaused) + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), + color: Colors.blue, + onPressed: cameraController != null && + cameraController.value.isInitialized && + cameraController.value.isRecordingVideo + ? (cameraController.value.isRecordingPaused) + ? onResumeButtonPressed + : onPauseButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.stop), + color: Colors.red, + onPressed: cameraController != null && + cameraController.value.isInitialized && + cameraController.value.isRecordingVideo + ? onStopButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.pause_presentation), + color: + cameraController != null && cameraController.value.isPreviewPaused + ? Colors.red + : Colors.blue, + onPressed: + cameraController == null ? null : onPausePreviewButtonPressed, + ), + ], + ); + } + + /// Display a row of toggle to select the camera (or a message if no camera is available). + Widget _cameraTogglesRowWidget() { + final List toggles = []; + + void onChanged(CameraDescription? description) { + if (description == null) { + return; + } + + onNewCameraSelected(description); + } + + if (_cameras.isEmpty) { + _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async { + showInSnackBar('No camera found.'); + }); + return const Text('None'); + } else { + for (final CameraDescription cameraDescription in _cameras) { + toggles.add( + SizedBox( + width: 90.0, + child: RadioListTile( + title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), + groupValue: controller?.description, + value: cameraDescription, + onChanged: onChanged, + ), + ), + ); + } + } + + return Row(children: toggles); + } + + String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); + + void showInSnackBar(String message) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(message))); + } + + void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { + if (controller == null) { + return; + } + + final CameraController cameraController = controller!; + + final Point point = Point( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + ); + CameraPlatform.instance.setExposurePoint(cameraController.cameraId, point); + CameraPlatform.instance.setFocusPoint(cameraController.cameraId, point); + } + + Future onNewCameraSelected(CameraDescription cameraDescription) async { + if (controller != null) { + return controller!.setDescription(cameraDescription); + } else { + return _initializeCameraController(cameraDescription); + } + } + + Future _initializeCameraController( + CameraDescription cameraDescription) async { + final CameraController cameraController = CameraController( + cameraDescription, + kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, + enableAudio: enableAudio, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + controller = cameraController; + + // If the controller is updated then update the UI. + cameraController.addListener(() { + if (mounted) { + setState(() {}); + } + }); + + try { + await cameraController.initialize(); + await Future.wait(>[ + // The exposure mode is currently not supported on the web. + ...!kIsWeb + ? >[ + CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId) + .then( + (double value) => _minAvailableExposureOffset = value), + CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId) + .then((double value) => _maxAvailableExposureOffset = value) + ] + : >[], + CameraPlatform.instance + .getMaxZoomLevel(cameraController.cameraId) + .then((double value) => _maxAvailableZoom = value), + CameraPlatform.instance + .getMinZoomLevel(cameraController.cameraId) + .then((double value) => _minAvailableZoom = value), + ]); + } on CameraException catch (e) { + switch (e.code) { + case 'CameraAccessDenied': + showInSnackBar('You have denied camera access.'); + break; + case 'CameraAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable camera access.'); + break; + case 'CameraAccessRestricted': + // iOS only + showInSnackBar('Camera access is restricted.'); + break; + case 'AudioAccessDenied': + showInSnackBar('You have denied audio access.'); + break; + case 'AudioAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable audio access.'); + break; + case 'AudioAccessRestricted': + // iOS only + showInSnackBar('Audio access is restricted.'); + break; + case 'cameraPermission': + // Android & web only + showInSnackBar('Unknown permission error.'); + break; + default: + _showCameraException(e); + break; + } + } + + if (mounted) { + setState(() {}); + } + } + + void onTakePictureButtonPressed() { + takePicture().then((XFile? file) { + if (mounted) { + setState(() { + imageFile = file; + videoController?.dispose(); + videoController = null; + }); + if (file != null) { + showInSnackBar('Picture saved to ${file.path}'); + } + } + }); + } + + void onFlashModeButtonPressed() { + if (_flashModeControlRowAnimationController.value == 1) { + _flashModeControlRowAnimationController.reverse(); + } else { + _flashModeControlRowAnimationController.forward(); + _exposureModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onExposureModeButtonPressed() { + if (_exposureModeControlRowAnimationController.value == 1) { + _exposureModeControlRowAnimationController.reverse(); + } else { + _exposureModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onFocusModeButtonPressed() { + if (_focusModeControlRowAnimationController.value == 1) { + _focusModeControlRowAnimationController.reverse(); + } else { + _focusModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _exposureModeControlRowAnimationController.reverse(); + } + } + + void onAudioModeButtonPressed() { + enableAudio = !enableAudio; + if (controller != null) { + onNewCameraSelected(controller!.description); + } + } + + Future onCaptureOrientationLockButtonPressed() async { + try { + if (controller != null) { + final CameraController cameraController = controller!; + if (cameraController.value.isCaptureOrientationLocked) { + await cameraController.unlockCaptureOrientation(); + showInSnackBar('Capture orientation unlocked'); + } else { + await cameraController.lockCaptureOrientation(); + showInSnackBar( + 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + } + } + } on CameraException catch (e) { + _showCameraException(e); + } + } + + void onSetFlashModeButtonPressed(FlashMode mode) { + setFlashMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); + }); + } + + void onSetExposureModeButtonPressed(ExposureMode mode) { + setExposureMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); + }); + } + + void onSetFocusModeButtonPressed(FocusMode mode) { + setFocusMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); + }); + } + + void onVideoRecordButtonPressed() { + startVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + }); + } + + void onStopButtonPressed() { + stopVideoRecording().then((XFile? file) { + if (mounted) { + setState(() {}); + } + if (file != null) { + showInSnackBar('Video recorded to ${file.path}'); + videoFile = file; + _startVideoPlayer(); + } + }); + } + + Future onPausePreviewButtonPressed() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isPreviewPaused) { + await cameraController.resumePreview(); + } else { + await cameraController.pausePreview(); + } + + if (mounted) { + setState(() {}); + } + } + + void onPauseButtonPressed() { + pauseVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Video recording paused'); + }); + } + + void onResumeButtonPressed() { + resumeVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Video recording resumed'); + }); + } + + Future startVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isRecordingVideo) { + // A recording is already started, do nothing. + return; + } + + try { + await cameraController.startVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + return; + } + } + + Future stopVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return null; + } + + try { + return cameraController.stopVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + Future pauseVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return; + } + + try { + await cameraController.pauseVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future resumeVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return; + } + + try { + await cameraController.resumeVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setFlashMode(FlashMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setFlashMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureMode(ExposureMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setExposureMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureOffset(double offset) async { + if (controller == null) { + return; + } + + setState(() { + _currentExposureOffset = offset; + }); + try { + offset = await controller!.setExposureOffset(offset); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setFocusMode(FocusMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setFocusMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future _startVideoPlayer() async { + if (videoFile == null) { + return; + } + final VideoPlayerController vController; + if (Platform.operatingSystem == 'ohos') { + final FileSelector instance = FileSelector(); + int? fileFd = await instance.openFileByPath(videoFile!.path); + vController = VideoPlayerController.fileFd(fileFd!); + } else { + vController = kIsWeb? VideoPlayerController.network(videoFile!.path) + : VideoPlayerController.file(File(videoFile!.path)); + } + videoPlayerListener = () { + if (videoController != null) { + // Refreshing the state to update video player with the correct ratio. + if (mounted) { + setState(() {}); + } + videoController!.removeListener(videoPlayerListener!); + } + }; + vController.addListener(videoPlayerListener!); + await vController.setLooping(true); + await vController.initialize(); + await videoController?.dispose(); + if (mounted) { + setState(() { + imageFile = null; + videoController = vController; + }); + } + await vController.play(); + print("========_startVideoPlayer end: "+videoFile!.path); + } + + Future takePicture() async { + final CameraController? cameraController = controller; + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return null; + } + + if (cameraController.value.isTakingPicture) { + // A capture is already pending, do nothing. + return null; + } + + try { + final XFile file = await cameraController.takePicture(); + return file; + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + void _showCameraException(CameraException e) { + _logError(e.code, e.description); + showInSnackBar('Error: ${e.code}\n${e.description}'); + } +} + +/// CameraApp is the Main Application. +class CameraApp extends StatelessWidget { + /// Default Constructor + const CameraApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: CameraExampleHome(), + ); + } +} + +List _cameras = []; + +Future main() async { + // Fetch the available cameras before initializing the app. + try { + WidgetsFlutterBinding.ensureInitialized(); + _cameras = await CameraPlatform.instance.availableCameras(); + } on CameraException catch (e) { + _logError(e.code, e.description); + } + runApp(const CameraApp()); +} + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; -- Gitee From d13be1cf3eff9b5a28a74b8640de237403761d2b Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:07:45 +0800 Subject: [PATCH 03/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4camera=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- ohos/automatic_camera_test/.gitignore | 44 ++++ ohos/automatic_camera_test/.metadata | 43 ++++ .../.pluginToolsConfig.yaml | 17 ++ .../analysis_options.yaml | 43 ++++ ohos/automatic_camera_test/ohos/.gitignore | 19 ++ .../ohos/AppScope/app.json5 | 10 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../ohos/build-profile.json5 | 41 ++++ .../ohos/entry/.gitignore | 7 + .../ohos/entry/build-profile.json5 | 29 +++ .../ohos/entry/hvigorfile.ts | 17 ++ .../main/ets/entryability/EntryAbility.ets | 27 +++ .../main/ets/fileselector/FileSelector.ets | 82 ++++++++ .../fileselector/FileSelectorOhosPlugin.ets | 56 +++++ .../fileselector/GeneratedFileSelectorApi.ets | 194 ++++++++++++++++++ .../ets/plugins/GeneratedPluginRegistrant.ets | 32 +++ .../main/resources/base/element/color.json | 8 + .../src/ohosTest/ets/test/Ability.test.ets | 50 +++++ .../resources/base/element/color.json | 8 + .../ohos/hvigor/hvigor-config.json5 | 23 +++ ohos/automatic_camera_test/ohos/hvigorfile.ts | 21 ++ 21 files changed, 771 insertions(+) create mode 100644 ohos/automatic_camera_test/.gitignore create mode 100644 ohos/automatic_camera_test/.metadata create mode 100644 ohos/automatic_camera_test/.pluginToolsConfig.yaml create mode 100644 ohos/automatic_camera_test/analysis_options.yaml create mode 100644 ohos/automatic_camera_test/ohos/.gitignore create mode 100644 ohos/automatic_camera_test/ohos/AppScope/app.json5 create mode 100644 ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 ohos/automatic_camera_test/ohos/build-profile.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/.gitignore create mode 100644 ohos/automatic_camera_test/ohos/entry/build-profile.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/hvigorfile.ts create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelector.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 create mode 100644 ohos/automatic_camera_test/ohos/hvigorfile.ts diff --git a/ohos/automatic_camera_test/.gitignore b/ohos/automatic_camera_test/.gitignore new file mode 100644 index 00000000..24476c5d --- /dev/null +++ b/ohos/automatic_camera_test/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/ohos/automatic_camera_test/.metadata b/ohos/automatic_camera_test/.metadata new file mode 100644 index 00000000..b6470aff --- /dev/null +++ b/ohos/automatic_camera_test/.metadata @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + channel: dev + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + base_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + - platform: ohos + create_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + base_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/ohos/automatic_camera_test/.pluginToolsConfig.yaml b/ohos/automatic_camera_test/.pluginToolsConfig.yaml new file mode 100644 index 00000000..aa1cfb81 --- /dev/null +++ b/ohos/automatic_camera_test/.pluginToolsConfig.yaml @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +buildFlags: + _pluginToolsConfigGlobalKey: + - "--no-tree-shake-icons" + - "--dart-define=buildmode=testing" diff --git a/ohos/automatic_camera_test/analysis_options.yaml b/ohos/automatic_camera_test/analysis_options.yaml new file mode 100644 index 00000000..c7076b7e --- /dev/null +++ b/ohos/automatic_camera_test/analysis_options.yaml @@ -0,0 +1,43 @@ +# Copyright (c) 2024 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/ohos/automatic_camera_test/ohos/.gitignore b/ohos/automatic_camera_test/ohos/.gitignore new file mode 100644 index 00000000..ee3ce118 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/.gitignore @@ -0,0 +1,19 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +*.har +**/BuildProfile.ets +**/oh-package-lock.json5 + +**/src/main/resources/rawfile/flutter_assets/ +**/libs/arm64-v8a/libapp.so +**/libs/arm64-v8a/libflutter.so +**/libs/arm64-v8a/libvmservice_snapshot.so \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/AppScope/app.json5 b/ohos/automatic_camera_test/ohos/AppScope/app.json5 new file mode 100644 index 00000000..c144b41c --- /dev/null +++ b/ohos/automatic_camera_test/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "io.flutter.plugins.example", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png b/ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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: Promise { + try { + let file = await fs.open(path); + result.success(file.fd); + } catch (err) { + Log.e(TAG, 'open file failed with err: ' + err); + result.error(new Error("Failed to read file")); + } + } + + static getCodec(): MessageCodec { + return FileSelectorApiCodec.INSTANCE; + } + + setup(binaryMessenger: BinaryMessenger, abilityPluginBinding: AbilityPluginBinding): void { + let api = this; + { + this.binding = abilityPluginBinding; + const channel: BasicMessageChannel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.FileSelectorApi.openFileByPath", FileSelector.getCodec()); + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + Log.d(TAG, 'onMessage reply:' + reply) + const wrapped: Array = new Array(); + const args: Array = msg as Array; + const path = args[0] as string; + const resultCallback: Result = new ResultBuilder((result: number): void => { + wrapped.push(result); + reply.reply(wrapped); + },(error: Error): void => { + const wrappedError: ArrayList = msg.wrapError(error); + reply.reply(wrappedError); + }) + api.openFileByPath(path, resultCallback); + } + }); + } + } +} + +class ResultBuilder{ + success : (result: number)=>void + error: (error: Error) =>void + + constructor(success:ESObject , error:ESObject) { + this.success = success + this.error = error + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets new file mode 100644 index 00000000..2b3e4179 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import AbilityAware from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware'; +import { + AbilityPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding'; +import { + FlutterPlugin, + FlutterPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; +import { FileSelector } from './FileSelector' + +const TAG = "FileSelectorOhosPlugin" + +export default class FileSelectorOhosPlugin implements FlutterPlugin, AbilityAware { + + private pluginBinding: FlutterPluginBinding | null = null; + private fileSelectorApi: FileSelector | null = null; + + getUniqueClassName(): string { + return "FileSelectorOhosPlugin" + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + this.fileSelectorApi = new FileSelector(binding); + if (this.pluginBinding != null) { + this.fileSelectorApi.setup(this.pluginBinding.getBinaryMessenger(), binding); + } + } + + onDetachedFromAbility(): void { + this.fileSelectorApi = null; + } + + onAttachedToEngine(binding: FlutterPluginBinding): void { + console.debug(TAG, 'onAttachedToEngine file selector ') + this.pluginBinding = binding; + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + this.pluginBinding = null; + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets new file mode 100644 index 00000000..2d1f8cbe --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets @@ -0,0 +1,194 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { ByteBuffer } from '@ohos/flutter_ohos/src/main/ets/util/ByteBuffer'; +import StandardMessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec'; +import Log from '@ohos/flutter_ohos/src/main/ets/util/Log'; + +const TAG = "GeneratedFileSelectorApi"; + +class FlutterError extends Error { + /** The error code. */ + public code: string; + + /** The error details. Must be a datatype supported by the api codec. */ + public details: ESObject; + + constructor(code: string, message: string, details: ESObject) { + super(message); + this.code = code; + this.details = details; + } +} + +export function wrapError(exception: Error): Array { + let errorList: Array = new Array(); + if (exception instanceof FlutterError) { + let error = exception as FlutterError; + errorList.push(error.code); + errorList.push(error.message); + errorList.push(error.details); + } else { + errorList.push(exception.toString()); + errorList.push(exception.name); + errorList.push( + "Cause: " + exception.message + ", Stacktrace: " + exception.stack); + } + return errorList; +} + +export class FileResponse { + private path: string; + + public getPath(): string { + return this.path; + } + + public setPath(setterArg: string): void { + if (setterArg == null) { + throw new Error('Nonnull field \'path\' is null.'); + } + this.path = setterArg; + } + + private name: string; + + public getName(): string { + return this.name; + } + + public setName(setterArg: string): void { + this.name = setterArg; + } + + private fd: number; + + public getFd(): number { + return this.fd; + } + + public setFd(setterArg: number): void { + if (setterArg == null) { + throw new Error("Nonnull field \"fd\" is null."); + } + this.fd = setterArg; + } + + constructor(path: string, name: string, fd: number) { + this.path = path; + this.name = name; + this.fd = fd; + } + + toList(): Array { + let toListResult: Array = new Array(); + toListResult.push(this.path); + toListResult.push(this.name); + toListResult.push(this.fd); + return toListResult; + } + + static fromList(list: Array): FileResponse { + let path: ESObject = list[0]; + let name: ESObject = list[1]; + let fd: ESObject = list[2]; + let response = new FileResponse(path, name, fd); + return response; + } +} + +export class FileTypes { + mimeTypes: Array = []; + + getMimeTypes(): Array { + return this.mimeTypes; + } + + setMimeTypes(setterArg: Array | null): void { + if (setterArg == null) { + throw new Error("Nonnull field \"mimeTypes\" is null."); + } + this.mimeTypes = setterArg; + } + + extensions: Array = []; + + getExtensions(): Array { + return this.extensions; + } + + setExtensions(setterArg: Array): void { + if (setterArg == null) { + throw new Error("Nonnull field \"extensions\" is null."); + } + this.extensions = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + FileTypes() {} + + toList(): Array { + let toListResult: Array = new Array(); + toListResult.push(this.mimeTypes); + toListResult.push(this.extensions); + return toListResult; + } + + static fromList(list: Array): FileTypes { + let pigeonResult = new FileTypes(); + let mimeTypes: ESObject = list[0]; + pigeonResult.setMimeTypes(mimeTypes as Array); + let extensions: ESObject = list[1]; + pigeonResult.setExtensions(extensions as Array); + return pigeonResult; + } +} + + +export interface Result { + success(result: T): void; + error(error: Error): void; +} + +export class FileSelectorApiCodec extends StandardMessageCodec { + public static INSTANCE = new FileSelectorApiCodec(); + + readValueOfType(type: number, buffer: ByteBuffer): ESObject { + switch (type) { + case 128: + let res0 = FileResponse.fromList(super.readValue(buffer) as Array); + return res0; + case 129: + let vur: ESObject = super.readValue(buffer) + let res1 = FileTypes.fromList(vur as Array); + return res1; + default: + let res2: ESObject = super.readValueOfType(type, buffer); + return res2; + } + } + + writeValue(stream: ByteBuffer, value: ESObject): ESObject { + if (value instanceof FileResponse) { + stream.writeInt8(128); + return this.writeValue(stream, (value as FileResponse).toList()); + } else if (value instanceof FileTypes) { + stream.writeInt8(129); + return this.writeValue(stream, (value as FileTypes).toList()); + } else { + return super.writeValue(stream, value); + } + } + } diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets new file mode 100644 index 00000000..82e171e4 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets @@ -0,0 +1,32 @@ +import { FlutterEngine, Log } from '@ohos/flutter_ohos'; +import CameraPlugin from 'camera_ohos'; +import IntegrationTestPlugin from 'integration_test'; +import PathProviderPlugin from 'path_provider_ohos'; +import VideoPlayerPlugin from 'video_player_ohos'; + +/** + * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Ohos platform. + */ + +const TAG = "GeneratedPluginRegistrant"; + +export class GeneratedPluginRegistrant { + + static registerWith(flutterEngine: FlutterEngine) { + try { + flutterEngine.getPlugins()?.add(new CameraPlugin()); + flutterEngine.getPlugins()?.add(new IntegrationTestPlugin()); + flutterEngine.getPlugins()?.add(new PathProviderPlugin()); + flutterEngine.getPlugins()?.add(new VideoPlayerPlugin()); + } catch (e) { + Log.e( + TAG, + "Tried to register plugins with FlutterEngine (" + + flutterEngine + + ") failed."); + Log.e(TAG, "Received exception while registering", e); + } + } +} diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 00000000..3c712962 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 00000000..25d4c71f --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' + +export default function abilityTest() { + describe('ActsAbilityTest', function () { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(function () { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(function () { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(function () { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(function () { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain',0, function () { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc' + let b = 'b' + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b) + expect(a).assertEqual(a) + }) + }) +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 00000000..3c712962 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 b/ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 00000000..3323568c --- /dev/null +++ b/ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +/** +* Copyright (c) 2024 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "properties": { + "ohos.nativeResolver": false + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/hvigorfile.ts b/ohos/automatic_camera_test/ohos/hvigorfile.ts new file mode 100644 index 00000000..44f73712 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/hvigorfile.ts @@ -0,0 +1,21 @@ +/** +* Copyright (c) 2024 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file -- Gitee From 29c2f9ad370216640f5c063405067397e6da9c4f Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 11:14:03 +0000 Subject: [PATCH 04/18] =?UTF-8?q?Revert=20"fix:=20=E6=8F=90=E4=BA=A4camera?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d13be1cf3eff9b5a28a74b8640de237403761d2b. --- ohos/automatic_camera_test/.gitignore | 44 ---- ohos/automatic_camera_test/.metadata | 43 ---- .../.pluginToolsConfig.yaml | 17 -- .../analysis_options.yaml | 43 ---- ohos/automatic_camera_test/ohos/.gitignore | 19 -- .../ohos/AppScope/app.json5 | 10 - .../resources/base/media/app_icon.png | Bin 6790 -> 0 bytes .../ohos/build-profile.json5 | 41 ---- .../ohos/entry/.gitignore | 7 - .../ohos/entry/build-profile.json5 | 29 --- .../ohos/entry/hvigorfile.ts | 17 -- .../main/ets/entryability/EntryAbility.ets | 27 --- .../main/ets/fileselector/FileSelector.ets | 82 -------- .../fileselector/FileSelectorOhosPlugin.ets | 56 ----- .../fileselector/GeneratedFileSelectorApi.ets | 194 ------------------ .../ets/plugins/GeneratedPluginRegistrant.ets | 32 --- .../main/resources/base/element/color.json | 8 - .../src/ohosTest/ets/test/Ability.test.ets | 50 ----- .../resources/base/element/color.json | 8 - .../ohos/hvigor/hvigor-config.json5 | 23 --- ohos/automatic_camera_test/ohos/hvigorfile.ts | 21 -- 21 files changed, 771 deletions(-) delete mode 100644 ohos/automatic_camera_test/.gitignore delete mode 100644 ohos/automatic_camera_test/.metadata delete mode 100644 ohos/automatic_camera_test/.pluginToolsConfig.yaml delete mode 100644 ohos/automatic_camera_test/analysis_options.yaml delete mode 100644 ohos/automatic_camera_test/ohos/.gitignore delete mode 100644 ohos/automatic_camera_test/ohos/AppScope/app.json5 delete mode 100644 ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png delete mode 100644 ohos/automatic_camera_test/ohos/build-profile.json5 delete mode 100644 ohos/automatic_camera_test/ohos/entry/.gitignore delete mode 100644 ohos/automatic_camera_test/ohos/entry/build-profile.json5 delete mode 100644 ohos/automatic_camera_test/ohos/entry/hvigorfile.ts delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/entryability/EntryAbility.ets delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelector.ets delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets delete mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json delete mode 100644 ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 delete mode 100644 ohos/automatic_camera_test/ohos/hvigorfile.ts diff --git a/ohos/automatic_camera_test/.gitignore b/ohos/automatic_camera_test/.gitignore deleted file mode 100644 index 24476c5d..00000000 --- a/ohos/automatic_camera_test/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/ohos/automatic_camera_test/.metadata b/ohos/automatic_camera_test/.metadata deleted file mode 100644 index b6470aff..00000000 --- a/ohos/automatic_camera_test/.metadata +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 - channel: dev - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 - base_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 - - platform: ohos - create_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 - base_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/ohos/automatic_camera_test/.pluginToolsConfig.yaml b/ohos/automatic_camera_test/.pluginToolsConfig.yaml deleted file mode 100644 index aa1cfb81..00000000 --- a/ohos/automatic_camera_test/.pluginToolsConfig.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -buildFlags: - _pluginToolsConfigGlobalKey: - - "--no-tree-shake-icons" - - "--dart-define=buildmode=testing" diff --git a/ohos/automatic_camera_test/analysis_options.yaml b/ohos/automatic_camera_test/analysis_options.yaml deleted file mode 100644 index c7076b7e..00000000 --- a/ohos/automatic_camera_test/analysis_options.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2024 Huawei Device Co., Ltd. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/ohos/automatic_camera_test/ohos/.gitignore b/ohos/automatic_camera_test/ohos/.gitignore deleted file mode 100644 index ee3ce118..00000000 --- a/ohos/automatic_camera_test/ohos/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -/node_modules -/oh_modules -/local.properties -/.idea -**/build -/.hvigor -.cxx -/.clangd -/.clang-format -/.clang-tidy -**/.test -*.har -**/BuildProfile.ets -**/oh-package-lock.json5 - -**/src/main/resources/rawfile/flutter_assets/ -**/libs/arm64-v8a/libapp.so -**/libs/arm64-v8a/libflutter.so -**/libs/arm64-v8a/libvmservice_snapshot.so \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/AppScope/app.json5 b/ohos/automatic_camera_test/ohos/AppScope/app.json5 deleted file mode 100644 index c144b41c..00000000 --- a/ohos/automatic_camera_test/ohos/AppScope/app.json5 +++ /dev/null @@ -1,10 +0,0 @@ -{ - "app": { - "bundleName": "io.flutter.plugins.example", - "vendor": "example", - "versionCode": 1000000, - "versionName": "1.0.0", - "icon": "$media:app_icon", - "label": "$string:app_name" - } -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png b/ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png deleted file mode 100644 index ce307a8827bd75456441ceb57d530e4c8d45d36c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6790 zcmX|G1ymHk)?T_}Vd;>R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y): Promise { - try { - let file = await fs.open(path); - result.success(file.fd); - } catch (err) { - Log.e(TAG, 'open file failed with err: ' + err); - result.error(new Error("Failed to read file")); - } - } - - static getCodec(): MessageCodec { - return FileSelectorApiCodec.INSTANCE; - } - - setup(binaryMessenger: BinaryMessenger, abilityPluginBinding: AbilityPluginBinding): void { - let api = this; - { - this.binding = abilityPluginBinding; - const channel: BasicMessageChannel = new BasicMessageChannel( - binaryMessenger, "dev.flutter.FileSelectorApi.openFileByPath", FileSelector.getCodec()); - channel.setMessageHandler({ - onMessage(msg: ESObject, reply: Reply): void { - Log.d(TAG, 'onMessage reply:' + reply) - const wrapped: Array = new Array(); - const args: Array = msg as Array; - const path = args[0] as string; - const resultCallback: Result = new ResultBuilder((result: number): void => { - wrapped.push(result); - reply.reply(wrapped); - },(error: Error): void => { - const wrappedError: ArrayList = msg.wrapError(error); - reply.reply(wrappedError); - }) - api.openFileByPath(path, resultCallback); - } - }); - } - } -} - -class ResultBuilder{ - success : (result: number)=>void - error: (error: Error) =>void - - constructor(success:ESObject , error:ESObject) { - this.success = success - this.error = error - } -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets deleted file mode 100644 index 2b3e4179..00000000 --- a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets +++ /dev/null @@ -1,56 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import AbilityAware from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware'; -import { - AbilityPluginBinding -} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding'; -import { - FlutterPlugin, - FlutterPluginBinding -} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; -import { FileSelector } from './FileSelector' - -const TAG = "FileSelectorOhosPlugin" - -export default class FileSelectorOhosPlugin implements FlutterPlugin, AbilityAware { - - private pluginBinding: FlutterPluginBinding | null = null; - private fileSelectorApi: FileSelector | null = null; - - getUniqueClassName(): string { - return "FileSelectorOhosPlugin" - } - - onAttachedToAbility(binding: AbilityPluginBinding): void { - this.fileSelectorApi = new FileSelector(binding); - if (this.pluginBinding != null) { - this.fileSelectorApi.setup(this.pluginBinding.getBinaryMessenger(), binding); - } - } - - onDetachedFromAbility(): void { - this.fileSelectorApi = null; - } - - onAttachedToEngine(binding: FlutterPluginBinding): void { - console.debug(TAG, 'onAttachedToEngine file selector ') - this.pluginBinding = binding; - } - - onDetachedFromEngine(binding: FlutterPluginBinding): void { - this.pluginBinding = null; - } -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets deleted file mode 100644 index 2d1f8cbe..00000000 --- a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets +++ /dev/null @@ -1,194 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import { ByteBuffer } from '@ohos/flutter_ohos/src/main/ets/util/ByteBuffer'; -import StandardMessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec'; -import Log from '@ohos/flutter_ohos/src/main/ets/util/Log'; - -const TAG = "GeneratedFileSelectorApi"; - -class FlutterError extends Error { - /** The error code. */ - public code: string; - - /** The error details. Must be a datatype supported by the api codec. */ - public details: ESObject; - - constructor(code: string, message: string, details: ESObject) { - super(message); - this.code = code; - this.details = details; - } -} - -export function wrapError(exception: Error): Array { - let errorList: Array = new Array(); - if (exception instanceof FlutterError) { - let error = exception as FlutterError; - errorList.push(error.code); - errorList.push(error.message); - errorList.push(error.details); - } else { - errorList.push(exception.toString()); - errorList.push(exception.name); - errorList.push( - "Cause: " + exception.message + ", Stacktrace: " + exception.stack); - } - return errorList; -} - -export class FileResponse { - private path: string; - - public getPath(): string { - return this.path; - } - - public setPath(setterArg: string): void { - if (setterArg == null) { - throw new Error('Nonnull field \'path\' is null.'); - } - this.path = setterArg; - } - - private name: string; - - public getName(): string { - return this.name; - } - - public setName(setterArg: string): void { - this.name = setterArg; - } - - private fd: number; - - public getFd(): number { - return this.fd; - } - - public setFd(setterArg: number): void { - if (setterArg == null) { - throw new Error("Nonnull field \"fd\" is null."); - } - this.fd = setterArg; - } - - constructor(path: string, name: string, fd: number) { - this.path = path; - this.name = name; - this.fd = fd; - } - - toList(): Array { - let toListResult: Array = new Array(); - toListResult.push(this.path); - toListResult.push(this.name); - toListResult.push(this.fd); - return toListResult; - } - - static fromList(list: Array): FileResponse { - let path: ESObject = list[0]; - let name: ESObject = list[1]; - let fd: ESObject = list[2]; - let response = new FileResponse(path, name, fd); - return response; - } -} - -export class FileTypes { - mimeTypes: Array = []; - - getMimeTypes(): Array { - return this.mimeTypes; - } - - setMimeTypes(setterArg: Array | null): void { - if (setterArg == null) { - throw new Error("Nonnull field \"mimeTypes\" is null."); - } - this.mimeTypes = setterArg; - } - - extensions: Array = []; - - getExtensions(): Array { - return this.extensions; - } - - setExtensions(setterArg: Array): void { - if (setterArg == null) { - throw new Error("Nonnull field \"extensions\" is null."); - } - this.extensions = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - FileTypes() {} - - toList(): Array { - let toListResult: Array = new Array(); - toListResult.push(this.mimeTypes); - toListResult.push(this.extensions); - return toListResult; - } - - static fromList(list: Array): FileTypes { - let pigeonResult = new FileTypes(); - let mimeTypes: ESObject = list[0]; - pigeonResult.setMimeTypes(mimeTypes as Array); - let extensions: ESObject = list[1]; - pigeonResult.setExtensions(extensions as Array); - return pigeonResult; - } -} - - -export interface Result { - success(result: T): void; - error(error: Error): void; -} - -export class FileSelectorApiCodec extends StandardMessageCodec { - public static INSTANCE = new FileSelectorApiCodec(); - - readValueOfType(type: number, buffer: ByteBuffer): ESObject { - switch (type) { - case 128: - let res0 = FileResponse.fromList(super.readValue(buffer) as Array); - return res0; - case 129: - let vur: ESObject = super.readValue(buffer) - let res1 = FileTypes.fromList(vur as Array); - return res1; - default: - let res2: ESObject = super.readValueOfType(type, buffer); - return res2; - } - } - - writeValue(stream: ByteBuffer, value: ESObject): ESObject { - if (value instanceof FileResponse) { - stream.writeInt8(128); - return this.writeValue(stream, (value as FileResponse).toList()); - } else if (value instanceof FileTypes) { - stream.writeInt8(129); - return this.writeValue(stream, (value as FileTypes).toList()); - } else { - return super.writeValue(stream, value); - } - } - } diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets deleted file mode 100644 index 82e171e4..00000000 --- a/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets +++ /dev/null @@ -1,32 +0,0 @@ -import { FlutterEngine, Log } from '@ohos/flutter_ohos'; -import CameraPlugin from 'camera_ohos'; -import IntegrationTestPlugin from 'integration_test'; -import PathProviderPlugin from 'path_provider_ohos'; -import VideoPlayerPlugin from 'video_player_ohos'; - -/** - * Generated file. Do not edit. - * This file is generated by the Flutter tool based on the - * plugins that support the Ohos platform. - */ - -const TAG = "GeneratedPluginRegistrant"; - -export class GeneratedPluginRegistrant { - - static registerWith(flutterEngine: FlutterEngine) { - try { - flutterEngine.getPlugins()?.add(new CameraPlugin()); - flutterEngine.getPlugins()?.add(new IntegrationTestPlugin()); - flutterEngine.getPlugins()?.add(new PathProviderPlugin()); - flutterEngine.getPlugins()?.add(new VideoPlayerPlugin()); - } catch (e) { - Log.e( - TAG, - "Tried to register plugins with FlutterEngine (" - + flutterEngine - + ") failed."); - Log.e(TAG, "Received exception while registering", e); - } - } -} diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json deleted file mode 100644 index 3c712962..00000000 --- a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "color": [ - { - "name": "start_window_background", - "value": "#FFFFFF" - } - ] -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets deleted file mode 100644 index 25d4c71f..00000000 --- a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets +++ /dev/null @@ -1,50 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import hilog from '@ohos.hilog'; -import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' - -export default function abilityTest() { - describe('ActsAbilityTest', function () { - // Defines a test suite. Two parameters are supported: test suite name and test suite function. - beforeAll(function () { - // Presets an action, which is performed only once before all test cases of the test suite start. - // This API supports only one parameter: preset action function. - }) - beforeEach(function () { - // Presets an action, which is performed before each unit test case starts. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: preset action function. - }) - afterEach(function () { - // Presets a clear action, which is performed after each unit test case ends. - // The number of execution times is the same as the number of test cases defined by **it**. - // This API supports only one parameter: clear action function. - }) - afterAll(function () { - // Presets a clear action, which is performed after all test cases of the test suite end. - // This API supports only one parameter: clear action function. - }) - it('assertContain',0, function () { - // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. - hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); - let a = 'abc' - let b = 'b' - // Defines a variety of assertion methods, which are used to declare expected boolean conditions. - expect(a).assertContain(b) - expect(a).assertEqual(a) - }) - }) -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json deleted file mode 100644 index 3c712962..00000000 --- a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "color": [ - { - "name": "start_window_background", - "value": "#FFFFFF" - } - ] -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 b/ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 deleted file mode 100644 index 3323568c..00000000 --- a/ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 +++ /dev/null @@ -1,23 +0,0 @@ -/** -* Copyright (c) 2024 Huawei Device Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -{ - "modelVersion": "5.0.0", - "dependencies": { - }, - "properties": { - "ohos.nativeResolver": false - } -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/hvigorfile.ts b/ohos/automatic_camera_test/ohos/hvigorfile.ts deleted file mode 100644 index 44f73712..00000000 --- a/ohos/automatic_camera_test/ohos/hvigorfile.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** -* Copyright (c) 2024 Huawei Device Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import { appTasks } from '@ohos/hvigor-ohos-plugin'; - -export default { - system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ -} \ No newline at end of file -- Gitee From 9cabab341522fb1d88540143f592e7b4013530c7 Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 11:15:44 +0000 Subject: [PATCH 05/18] =?UTF-8?q?Revert=20"fix:=20=E6=8F=90=E4=BA=A4camera?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ea3aec66d777ede7305639ba47298abefd93888a. --- .../integration_test/camera_test.dart | 489 ------------------ 1 file changed, 489 deletions(-) delete mode 100644 ohos/automatic_camera_test/integration_test/camera_test.dart diff --git a/ohos/automatic_camera_test/integration_test/camera_test.dart b/ohos/automatic_camera_test/integration_test/camera_test.dart deleted file mode 100644 index 7870f8c6..00000000 --- a/ohos/automatic_camera_test/integration_test/camera_test.dart +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; -import 'dart:ui' as ui show Image; - -import 'package:camera_ohos/camera_ohos.dart'; -import 'package:automatic_camera_test/camera_controller.dart'; -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/painting.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:video_player/video_player.dart'; - -void main() { - late Directory testDir; - - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - setUpAll(() async { - CameraPlatform.instance = OhosCamera(); - final Directory extDir = await getTemporaryDirectory(); - testDir = await Directory('${extDir.path}/integration_test').create(recursive: true); - }); - - tearDownAll(() async { - await testDir.delete(recursive: true); - }); - - final Map presetExpectedSizes = - { - ResolutionPreset.low: const Size(240, 320), - ResolutionPreset.medium: const Size(480, 720), - ResolutionPreset.high: const Size(720, 1280), - ResolutionPreset.veryHigh: const Size(1080, 1920), - ResolutionPreset.ultraHigh: const Size(2160, 3840), - // Don't bother checking for max here since it could be anything. - }; - - /// Verify that [actual] has dimensions that are at least as large as - /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns - /// whether the dimensions exactly match. - bool assertExpectedDimensions(Size expectedSize, Size actual) { - expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); - expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); - return actual.shortestSide == expectedSize.shortestSide && - actual.longestSide == expectedSize.longestSide; - } - - // This tests that the capture is no bigger than the preset, since we have - // automatic code to fall back to smaller sizes when we need to. Returns - // whether the image is exactly the desired resolution. - Future testCaptureImageResolution( - CameraController controller, ResolutionPreset preset) async { - final Size expectedSize = presetExpectedSizes[preset]!; - - // Take Picture - final XFile file = await controller.takePicture(); - - // Load picture - final File fileImage = File(file.path); - final Image image = (await decodeImageFromList(fileImage.readAsBytesSync())) as Image; - - // Verify image dimensions are as expected - expect(image, isNotNull); - return assertExpectedDimensions( - expectedSize, Size(image.height as double, image.width as double)); - } - - testWidgets( - 'Capture specific image resolutions', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - for (final CameraDescription cameraDescription in cameras) { - bool previousPresetExactlySupported = true; - for (final MapEntry preset - in presetExpectedSizes.entries) { - final CameraController controller = - CameraController(cameraDescription, preset.key); - await controller.initialize(); - final bool presetExactlySupported = - await testCaptureImageResolution(controller, preset.key); - assert(!(!previousPresetExactlySupported && presetExactlySupported), - 'The camera took higher resolution pictures at a lower resolution.'); - previousPresetExactlySupported = presetExactlySupported; - await controller.dispose(); - } - } - }, - // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. - skip: true, - ); - - // This tests that the capture is no bigger than the preset, since we have - // automatic code to fall back to smaller sizes when we need to. Returns - // whether the image is exactly the desired resolution. - Future testCaptureVideoResolution( - CameraController controller, ResolutionPreset preset) async { - final Size expectedSize = presetExpectedSizes[preset]!; - - // Take Video - await controller.startVideoRecording(); - sleep(const Duration(milliseconds: 300)); - final XFile file = await controller.stopVideoRecording(); - - // Load video metadata - final File videoFile = File(file.path); - final VideoPlayerController videoController = - VideoPlayerController.file(videoFile); - await videoController.initialize(); - final Size video = videoController.value.size; - - // Verify image dimensions are as expected - expect(video, isNotNull); - return assertExpectedDimensions( - expectedSize, Size(video.height, video.width)); - } - - testWidgets( - 'Capture specific video resolutions', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - for (final CameraDescription cameraDescription in cameras) { - bool previousPresetExactlySupported = true; - for (final MapEntry preset - in presetExpectedSizes.entries) { - final CameraController controller = - CameraController(cameraDescription, preset.key); - await controller.initialize(); - await controller.prepareForVideoRecording(); - final bool presetExactlySupported = - await testCaptureVideoResolution(controller, preset.key); - assert(!(!previousPresetExactlySupported && presetExactlySupported), - 'The camera took higher resolution pictures at a lower resolution.'); - previousPresetExactlySupported = presetExactlySupported; - await controller.dispose(); - } - } - }, - // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. - skip: true, - ); - - testWidgets('Pause and resume video recording', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - await controller.prepareForVideoRecording(); - - int startPause; - int timePaused = 0; - - await controller.startVideoRecording(); - final int recordingStart = DateTime.now().millisecondsSinceEpoch; - sleep(const Duration(milliseconds: 500)); - - await controller.pauseVideoRecording(); - startPause = DateTime.now().millisecondsSinceEpoch; - sleep(const Duration(milliseconds: 500)); - await controller.resumeVideoRecording(); - timePaused += DateTime.now().millisecondsSinceEpoch - startPause; - - sleep(const Duration(milliseconds: 500)); - - await controller.pauseVideoRecording(); - startPause = DateTime.now().millisecondsSinceEpoch; - sleep(const Duration(milliseconds: 500)); - await controller.resumeVideoRecording(); - timePaused += DateTime.now().millisecondsSinceEpoch - startPause; - - sleep(const Duration(milliseconds: 500)); - - final XFile file = await controller.stopVideoRecording(); - - final int recordingTime = - DateTime.now().millisecondsSinceEpoch - recordingStart; - - final File videoFile = File(file.path); - final VideoPlayerController videoController = VideoPlayerController.file( - videoFile, - ); - await videoController.initialize(); - final int duration = videoController.value.duration.inMilliseconds; - await videoController.dispose(); - - print("====Pause and resume video recording-" - "${(duration < (recordingTime - timePaused)) == true ? "success" : "failed"}:"); - expect(duration, lessThan(recordingTime - timePaused)); - try { - controller.dispose(); - } on Exception catch (e) { - print(""); - } - }); - - testWidgets('Set description while recording', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.length < 2) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - await controller.prepareForVideoRecording(); - - await controller.startVideoRecording(); - - // SDK < 26 will throw a platform error when trying to switch and keep the same camera - // we accept either outcome here, while the native unit tests check the outcome based on the current Android SDK - bool failed = false; - try { - await controller.setDescription(cameras[1]); - } catch (err) { - // expect(err, isA()); - // expect( - // (err as PlatformException).message, - // equals( - // 'Device does not support switching the camera while recording')); - failed = true; - } - - if (failed) { - // cameras did not switch - print("====Set description while recording-" - "${(controller.description==cameras[0]) == true ? "success" : "failed"}:"); - // expect(controller.description, cameras[0]); - } else { - // cameras switched - print("====Set description while recording-" - "${(controller.description==cameras[1]) == true ? "success" : "failed"}:"); - // expect(controller.description, cameras[1]); - } - try { - await controller.dispose(); - } on Exception catch (e) { - print(""); - } - }); - - testWidgets('Set description', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.length < 2) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - await controller.setDescription(cameras[1]); - - print("====Set description-" - "${(controller.description==cameras[1]) == true ? "success" : "failed"}:"); - expect(controller.description, cameras[1]); - - try { - await controller.dispose(); - } on Exception catch (e) { - print(""); - } - }); - - testWidgets( - 'image streaming', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - bool isDetecting = false; - - await controller.startImageStream((CameraImageData image) { - if (isDetecting) { - return; - } - - isDetecting = true; - - expectLater(image, isNotNull).whenComplete(() => isDetecting = false); - }); - - print("====image streaming-" - "${controller.value.isStreamingImages == true ? "success" : "failed"}:"); - expect(controller.value.isStreamingImages, true); - - sleep(const Duration(milliseconds: 500)); - - await controller.stopImageStream(); - try { - await controller.dispose(); - } on Exception catch (e) { - print(""); - } - }, - ); - - testWidgets( - 'recording with image stream', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - bool isDetecting = false; - - await controller.startVideoRecording( - streamCallback: (CameraImageData image) { - if (isDetecting) { - return; - } - - isDetecting = true; - - expectLater(image, isNotNull); - }); - - var flag = controller.value.isStreamingImages == true; - expect(controller.value.isStreamingImages, true); - - await Future.delayed(const Duration(seconds: 2)); - - await controller.stopVideoRecording(); - try { - await controller.dispose(); - } on Exception catch (e) { - print(""); - } - print("====recording with image stream-" - "${(controller.value.isStreamingImages == false) && flag == true ? "success" : "failed"}:"); - expect(controller.value.isStreamingImages, false); - }, - ); - - testWidgets( - 'takePicture', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - await controller.initialize(); - - // Take Picture - final XFile file = await controller.takePicture(); - // Load picture - final File fileImage = File(file.path); - - final ui.Image image = await decodeImageFromList(fileImage.readAsBytesSync()); - - print("====takePicture-" - "${(image != null) == true ? "success" : "failed"}:"); - try { - controller.dispose(); - } on Exception catch(e) { - print(""); - } - }, - ); - - testWidgets( - 'previewerMode', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - - final Widget widget = controller.buildPreview(); - - print("====previewerMode - build previewer :" - "${(widget as Texture).textureId == controller.cameraId ? "success" : "failed"}:"); - - await controller.pausePreview(); - - print("====previewerMode - paused previewer: " - "${(controller.value.isPreviewPaused) ? "success" : "failed"}:"); - - await controller.resumePreview(); - print("====previewerMode - resume previewer: " - "${(!controller.value.isPreviewPaused) ? "success" : "failed"}:"); - - try { - controller.dispose(); - } on Exception catch(e) { - print(""); - } - }, - ); - - testWidgets( - 'ExposureMode', - (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.isEmpty) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - await controller.setExposureMode(ExposureMode.auto); - - print("====ExposureMode - set ExposureMode :" - "${controller.value.exposureMode == ExposureMode.auto ? "success" : "failed"}:"); - - final offset = await controller.setExposureOffset(1.0); - - print("set offset is ${offset}"); - print("====ExposureMode - set ExposureOffset : " - "${(offset == 2.0) ? "success" : "failed"}:"); - - try { - controller.dispose(); - } on Exception catch(e) { - print(""); - } - }, - ); -} -- Gitee From 39142c7616399dcdcad214840e05ce978a494379 Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 11:16:13 +0000 Subject: [PATCH 06/18] =?UTF-8?q?Revert=20"fix:=20=E6=8F=90=E4=BA=A4camera?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ad4c506756863e14ba834419e48229cd2d4326d7. --- .../lib/camera_controller.dart | 574 --------- .../lib/camera_preview.dart | 87 -- .../lib/fileselector/file_selector.dart | 33 - .../lib/fileselector/file_selector_api.dart | 142 --- ohos/automatic_camera_test/lib/main.dart | 1087 ----------------- 5 files changed, 1923 deletions(-) delete mode 100644 ohos/automatic_camera_test/lib/camera_controller.dart delete mode 100644 ohos/automatic_camera_test/lib/camera_preview.dart delete mode 100644 ohos/automatic_camera_test/lib/fileselector/file_selector.dart delete mode 100644 ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart delete mode 100644 ohos/automatic_camera_test/lib/main.dart diff --git a/ohos/automatic_camera_test/lib/camera_controller.dart b/ohos/automatic_camera_test/lib/camera_controller.dart deleted file mode 100644 index 9ab06140..00000000 --- a/ohos/automatic_camera_test/lib/camera_controller.dart +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:collection'; - -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -/// The state of a [CameraController]. -class CameraValue { - /// Creates a new camera controller state. - const CameraValue({ - required this.isInitialized, - this.previewSize, - required this.isRecordingVideo, - required this.isTakingPicture, - required this.isStreamingImages, - required this.isRecordingPaused, - required this.flashMode, - required this.exposureMode, - required this.focusMode, - required this.deviceOrientation, - required this.description, - this.lockedCaptureOrientation, - this.recordingOrientation, - this.isPreviewPaused = false, - this.previewPauseOrientation, - }); - - /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized(CameraDescription description) - : this( - isInitialized: false, - isRecordingVideo: false, - isTakingPicture: false, - isStreamingImages: false, - isRecordingPaused: false, - flashMode: FlashMode.auto, - exposureMode: ExposureMode.auto, - focusMode: FocusMode.auto, - deviceOrientation: DeviceOrientation.portraitUp, - isPreviewPaused: false, - description: description, - ); - - /// True after [CameraController.initialize] has completed successfully. - final bool isInitialized; - - /// True when a picture capture request has been sent but as not yet returned. - final bool isTakingPicture; - - /// True when the camera is recording (not the same as previewing). - final bool isRecordingVideo; - - /// True when images from the camera are being streamed. - final bool isStreamingImages; - - /// True when video recording is paused. - final bool isRecordingPaused; - - /// True when the preview widget has been paused manually. - final bool isPreviewPaused; - - /// Set to the orientation the preview was paused in, if it is currently paused. - final DeviceOrientation? previewPauseOrientation; - - /// The size of the preview in pixels. - /// - /// Is `null` until [isInitialized] is `true`. - final Size? previewSize; - - /// The flash mode the camera is currently set to. - final FlashMode flashMode; - - /// The exposure mode the camera is currently set to. - final ExposureMode exposureMode; - - /// The focus mode the camera is currently set to. - final FocusMode focusMode; - - /// The current device UI orientation. - final DeviceOrientation deviceOrientation; - - /// The currently locked capture orientation. - final DeviceOrientation? lockedCaptureOrientation; - - /// Whether the capture orientation is currently locked. - bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; - - /// The orientation of the currently running video recording. - final DeviceOrientation? recordingOrientation; - - /// The properties of the camera device controlled by this controller. - final CameraDescription description; - - /// Creates a modified copy of the object. - /// - /// Explicitly specified fields get the specified value, all other fields get - /// the same value of the current object. - CameraValue copyWith({ - bool? isInitialized, - bool? isRecordingVideo, - bool? isTakingPicture, - bool? isStreamingImages, - Size? previewSize, - bool? isRecordingPaused, - FlashMode? flashMode, - ExposureMode? exposureMode, - FocusMode? focusMode, - bool? exposurePointSupported, - bool? focusPointSupported, - DeviceOrientation? deviceOrientation, - Optional? lockedCaptureOrientation, - Optional? recordingOrientation, - bool? isPreviewPaused, - CameraDescription? description, - Optional? previewPauseOrientation, - }) { - return CameraValue( - isInitialized: isInitialized ?? this.isInitialized, - previewSize: previewSize ?? this.previewSize, - isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, - isTakingPicture: isTakingPicture ?? this.isTakingPicture, - isStreamingImages: isStreamingImages ?? this.isStreamingImages, - isRecordingPaused: isRecordingPaused ?? this.isRecordingPaused, - flashMode: flashMode ?? this.flashMode, - exposureMode: exposureMode ?? this.exposureMode, - focusMode: focusMode ?? this.focusMode, - deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: lockedCaptureOrientation == null - ? this.lockedCaptureOrientation - : lockedCaptureOrientation.orNull, - recordingOrientation: recordingOrientation == null - ? this.recordingOrientation - : recordingOrientation.orNull, - isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - description: description ?? this.description, - previewPauseOrientation: previewPauseOrientation == null - ? this.previewPauseOrientation - : previewPauseOrientation.orNull, - ); - } - - @override - String toString() { - return '${objectRuntimeType(this, 'CameraValue')}(' - 'isRecordingVideo: $isRecordingVideo, ' - 'isInitialized: $isInitialized, ' - 'previewSize: $previewSize, ' - 'isStreamingImages: $isStreamingImages, ' - 'flashMode: $flashMode, ' - 'exposureMode: $exposureMode, ' - 'focusMode: $focusMode, ' - 'deviceOrientation: $deviceOrientation, ' - 'lockedCaptureOrientation: $lockedCaptureOrientation, ' - 'recordingOrientation: $recordingOrientation, ' - 'isPreviewPaused: $isPreviewPaused, ' - 'previewPausedOrientation: $previewPauseOrientation)'; - } -} - -/// Controls a device camera. -/// -/// This is a stripped-down version of the app-facing controller to serve as a -/// utility for the example and integration tests. It wraps only the calls that -/// have state associated with them, to consolidate tracking of camera state -/// outside of the overall example code. -class CameraController extends ValueNotifier { - /// Creates a new camera controller in an uninitialized state. - CameraController( - CameraDescription cameraDescription, - this.resolutionPreset, { - this.enableAudio = true, - this.imageFormatGroup, - }) : super(CameraValue.uninitialized(cameraDescription)); - - /// The properties of the camera device controlled by this controller. - CameraDescription get description => value.description; - - /// The resolution this controller is targeting. - /// - /// This resolution preset is not guaranteed to be available on the device, - /// if unavailable a lower resolution will be used. - /// - /// See also: [ResolutionPreset]. - final ResolutionPreset resolutionPreset; - - /// Whether to include audio when recording a video. - final bool enableAudio; - - /// The [ImageFormatGroup] describes the output of the raw image format. - /// - /// When null the imageFormat will fallback to the platforms default. - final ImageFormatGroup? imageFormatGroup; - - late int _cameraId; - - bool _isDisposed = false; - StreamSubscription? _imageStreamSubscription; - FutureOr? _initCalled; - StreamSubscription? - _deviceOrientationSubscription; - - /// The camera identifier with which the controller is associated. - int get cameraId => _cameraId; - - /// Initializes the camera on the device. - Future initialize() => _initializeWithDescription(description); - - Future _initializeWithDescription(CameraDescription description) async { - final Completer initializeCompleter = - Completer(); - - _deviceOrientationSubscription = CameraPlatform.instance - .onDeviceOrientationChanged() - .listen((DeviceOrientationChangedEvent event) { - value = value.copyWith( - deviceOrientation: event.orientation, - ); - }); - - _cameraId = await CameraPlatform.instance.createCamera( - description, - resolutionPreset, - enableAudio: enableAudio, - ); - - unawaited(CameraPlatform.instance - .onCameraInitialized(_cameraId) - .first - .then((CameraInitializedEvent event) { - initializeCompleter.complete(event); - })); - - await CameraPlatform.instance.initializeCamera( - _cameraId, - imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, - ); - - value = value.copyWith( - isInitialized: true, - description: description, - previewSize: await initializeCompleter.future - .then((CameraInitializedEvent event) => Size( - event.previewWidth, - event.previewHeight, - )), - exposureMode: await initializeCompleter.future - .then((CameraInitializedEvent event) => event.exposureMode), - focusMode: await initializeCompleter.future - .then((CameraInitializedEvent event) => event.focusMode), - exposurePointSupported: await initializeCompleter.future - .then((CameraInitializedEvent event) => event.exposurePointSupported), - focusPointSupported: await initializeCompleter.future - .then((CameraInitializedEvent event) => event.focusPointSupported), - ); - - _initCalled = true; - } - - /// Prepare the capture session for video recording. - Future prepareForVideoRecording() async { - await CameraPlatform.instance.prepareForVideoRecording(); - } - - /// Pauses the current camera preview - Future pausePreview() async { - await CameraPlatform.instance.pausePreview(_cameraId); - value = value.copyWith( - isPreviewPaused: true, - previewPauseOrientation: Optional.of( - value.lockedCaptureOrientation ?? value.deviceOrientation)); - } - - /// Resumes the current camera preview - Future resumePreview() async { - await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith( - isPreviewPaused: false, - previewPauseOrientation: const Optional.absent()); - } - - /// Sets the description of the camera. - Future setDescription(CameraDescription description) async { - if (value.isRecordingVideo) { - await CameraPlatform.instance.setDescriptionWhileRecording(description); - value = value.copyWith(description: description); - } else { - await _initializeWithDescription(description); - } - } - - /// Captures an image and returns the file where it was saved. - /// - /// Throws a [CameraException] if the capture fails. - Future takePicture() async { - value = value.copyWith(isTakingPicture: true); - final XFile file = await CameraPlatform.instance.takePicture(_cameraId); - value = value.copyWith(isTakingPicture: false); - return file; - } - - /// Start streaming images from platform camera. - Future startImageStream( - Function(CameraImageData image) onAvailable) async { - _imageStreamSubscription = CameraPlatform.instance - .onStreamedFrameAvailable(_cameraId) - .listen((CameraImageData imageData) { - onAvailable(imageData); - }); - value = value.copyWith(isStreamingImages: true); - } - - /// Stop streaming images from platform camera. - Future stopImageStream() async { - value = value.copyWith(isStreamingImages: false); - await _imageStreamSubscription?.cancel(); - _imageStreamSubscription = null; - } - - /// Start a video recording. - /// - /// The video is returned as a [XFile] after calling [stopVideoRecording]. - /// Throws a [CameraException] if the capture fails. - Future startVideoRecording( - {Function(CameraImageData image)? streamCallback}) async { - await CameraPlatform.instance.startVideoCapturing( - VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); - value = value.copyWith( - isRecordingVideo: true, - isRecordingPaused: false, - isStreamingImages: streamCallback != null, - recordingOrientation: Optional.of( - value.lockedCaptureOrientation ?? value.deviceOrientation)); - } - - /// Stops the video recording and returns the file where it was saved. - /// - /// Throws a [CameraException] if the capture failed. - Future stopVideoRecording() async { - if (value.isStreamingImages) { - await stopImageStream(); - } - - final XFile file = - await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith( - isRecordingVideo: false, - isRecordingPaused: false, - recordingOrientation: const Optional.absent(), - ); - return file; - } - - /// Pause video recording. - /// - /// This feature is only available on iOS and Android sdk 24+. - Future pauseVideoRecording() async { - await CameraPlatform.instance.pauseVideoRecording(_cameraId); - value = value.copyWith(isRecordingPaused: true); - } - - /// Resume video recording after pausing. - /// - /// This feature is only available on iOS and Android sdk 24+. - Future resumeVideoRecording() async { - await CameraPlatform.instance.resumeVideoRecording(_cameraId); - value = value.copyWith(isRecordingPaused: false); - } - - /// Returns a widget showing a live camera preview. - Widget buildPreview() { - return CameraPlatform.instance.buildPreview(_cameraId); - } - - /// Sets the flash mode for taking pictures. - Future setFlashMode(FlashMode mode) async { - await CameraPlatform.instance.setFlashMode(_cameraId, mode); - value = value.copyWith(flashMode: mode); - } - - /// Sets the exposure mode for taking pictures. - Future setExposureMode(ExposureMode mode) async { - await CameraPlatform.instance.setExposureMode(_cameraId, mode); - value = value.copyWith(exposureMode: mode); - } - - /// Sets the exposure offset for the selected camera. - Future setExposureOffset(double offset) async { - // Check if offset is in range - final List range = await Future.wait(>[ - CameraPlatform.instance.getMinExposureOffset(_cameraId), - CameraPlatform.instance.getMaxExposureOffset(_cameraId) - ]); - - // Round to the closest step if needed - final double stepSize = - await CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); - if (stepSize > 0) { - final double inv = 1.0 / stepSize; - double roundedOffset = (offset * inv).roundToDouble() / inv; - if (roundedOffset > range[1]) { - roundedOffset = (offset * inv).floorToDouble() / inv; - } else if (roundedOffset < range[0]) { - roundedOffset = (offset * inv).ceilToDouble() / inv; - } - offset = roundedOffset; - } - - return CameraPlatform.instance.setExposureOffset(_cameraId, offset); - } - - /// Locks the capture orientation. - /// - /// If [orientation] is omitted, the current device orientation is used. - Future lockCaptureOrientation() async { - await CameraPlatform.instance - .lockCaptureOrientation(_cameraId, value.deviceOrientation); - value = value.copyWith( - lockedCaptureOrientation: - Optional.of(value.deviceOrientation)); - } - - /// Unlocks the capture orientation. - Future unlockCaptureOrientation() async { - await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); - value = value.copyWith( - lockedCaptureOrientation: const Optional.absent()); - } - - /// Sets the focus mode for taking pictures. - Future setFocusMode(FocusMode mode) async { - await CameraPlatform.instance.setFocusMode(_cameraId, mode); - value = value.copyWith(focusMode: mode); - } - - /// Releases the resources of this camera. - @override - Future dispose() async { - if (_isDisposed) { - return; - } - _isDisposed = true; - await _deviceOrientationSubscription?.cancel(); - super.dispose(); - if (_initCalled != null) { - await _initCalled; - await CameraPlatform.instance.dispose(_cameraId); - } - } - - @override - void removeListener(VoidCallback listener) { - // Prevent ValueListenableBuilder in CameraPreview widget from causing an - // exception to be thrown by attempting to remove its own listener after - // the controller has already been disposed. - if (!_isDisposed) { - super.removeListener(listener); - } - } -} - -/// A value that might be absent. -/// -/// Used to represent [DeviceOrientation]s that are optional but also able -/// to be cleared. -@immutable -class Optional extends IterableBase { - /// Constructs an empty Optional. - const Optional.absent() : _value = null; - - /// Constructs an Optional of the given [value]. - /// - /// Throws [ArgumentError] if [value] is null. - Optional.of(T value) : _value = value { - // TODO(cbracken): Delete and make this ctor const once mixed-mode - // execution is no longer around. - ArgumentError.checkNotNull(value); - } - - /// Constructs an Optional of the given [value]. - /// - /// If [value] is null, returns [absent()]. - const Optional.fromNullable(T? value) : _value = value; - - final T? _value; - - /// True when this optional contains a value. - bool get isPresent => _value != null; - - /// True when this optional contains no value. - bool get isNotPresent => _value == null; - - /// Gets the Optional value. - /// - /// Throws [StateError] if [value] is null. - T get value { - if (_value == null) { - throw StateError('value called on absent Optional.'); - } - return _value!; - } - - /// Executes a function if the Optional value is present. - void ifPresent(void Function(T value) ifPresent) { - if (isPresent) { - ifPresent(_value as T); - } - } - - /// Execution a function if the Optional value is absent. - void ifAbsent(void Function() ifAbsent) { - if (!isPresent) { - ifAbsent(); - } - } - - /// Gets the Optional value with a default. - /// - /// The default is returned if the Optional is [absent()]. - /// - /// Throws [ArgumentError] if [defaultValue] is null. - T or(T defaultValue) { - return _value ?? defaultValue; - } - - /// Gets the Optional value, or `null` if there is none. - T? get orNull => _value; - - /// Transforms the Optional value. - /// - /// If the Optional is [absent()], returns [absent()] without applying the transformer. - /// - /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. - Optional transform(S Function(T value) transformer) { - return _value == null - ? Optional.absent() - : Optional.of(transformer(_value as T)); - } - - /// Transforms the Optional value. - /// - /// If the Optional is [absent()], returns [absent()] without applying the transformer. - /// - /// Returns [absent()] if the transformer returns `null`. - Optional transformNullable(S? Function(T value) transformer) { - return _value == null - ? Optional.absent() - : Optional.fromNullable(transformer(_value as T)); - } - - @override - Iterator get iterator => - isPresent ? [_value as T].iterator : Iterable.empty().iterator; - - /// Delegates to the underlying [value] hashCode. - @override - int get hashCode => _value.hashCode; - - /// Delegates to the underlying [value] operator==. - @override - bool operator ==(Object o) => o is Optional && o._value == _value; - - @override - String toString() { - return _value == null - ? 'Optional { absent }' - : 'Optional { value: $_value }'; - } -} diff --git a/ohos/automatic_camera_test/lib/camera_preview.dart b/ohos/automatic_camera_test/lib/camera_preview.dart deleted file mode 100644 index 629409cf..00000000 --- a/ohos/automatic_camera_test/lib/camera_preview.dart +++ /dev/null @@ -1,87 +0,0 @@ - // Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'dart:math' as math; - -import 'camera_controller.dart'; - -/// A widget showing a live camera preview. -class CameraPreview extends StatelessWidget { - /// Creates a preview widget for the given camera controller. - const CameraPreview(this.controller, {super.key, this.child}); - - /// The controller for the camera that the preview is shown for. - final CameraController controller; - - /// A widget to overlay on top of the camera preview - final Widget? child; - - @override - Widget build(BuildContext context) { - - return controller.value.isInitialized - ? ValueListenableBuilder( - valueListenable: controller, - builder: (BuildContext context, Object? value, Widget? child) { - final double cameraAspectRatio = - controller.value.previewSize!.width / - controller.value.previewSize!.height; - return AspectRatio( - aspectRatio: _isLandscape() - ? cameraAspectRatio - : (1 / cameraAspectRatio), - child: Stack( - fit: StackFit.expand, - children: [ - _wrapInRotatedBox(child: controller.buildPreview()), - child ?? Container(), - ], - ), - ); - }, - child: child, - ) - : Container(); - } - - Widget _wrapInRotatedBox({required Widget child}) { - if (kIsWeb || defaultTargetPlatform != TargetPlatform.ohos) { - return child; - } - - return RotatedBox( - quarterTurns: _getQuarterTurns(), - child: child, - ); - } - - bool _isLandscape() { - return [ - DeviceOrientation.landscapeLeft, - DeviceOrientation.landscapeRight - ].contains(_getApplicableOrientation()); - } - - int _getQuarterTurns() { - final Map turns = { - DeviceOrientation.portraitUp: 0, - DeviceOrientation.landscapeRight: 1, - DeviceOrientation.portraitDown: 2, - DeviceOrientation.landscapeLeft: 3, - }; - return turns[_getApplicableOrientation()]!; - } - - DeviceOrientation _getApplicableOrientation() { - return controller.value.isRecordingVideo - ? controller.value.recordingOrientation! - : (controller.value.previewPauseOrientation ?? - controller.value.lockedCaptureOrientation ?? - controller.value.deviceOrientation); - } -} diff --git a/ohos/automatic_camera_test/lib/fileselector/file_selector.dart b/ohos/automatic_camera_test/lib/fileselector/file_selector.dart deleted file mode 100644 index 693746d5..00000000 --- a/ohos/automatic_camera_test/lib/fileselector/file_selector.dart +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import 'file_selector_api.dart'; - -class FileSelector { - FileSelector({ FileSelectorApi? api}) - : _api = api ?? FileSelectorApi(); - - final FileSelectorApi _api; - - /// Registers this class as the implementation of the file_selector platform interface. - /// - @override - Future openFileByPath(String path) async { - final int? fd = await _api.openFileByPath(path); - print("openfile#"); - print(fd); - return fd; - } -} \ No newline at end of file diff --git a/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart b/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart deleted file mode 100644 index 8ff6df7c..00000000 --- a/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart +++ /dev/null @@ -1,142 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import 'dart:async'; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; - -class FileResponse { - FileResponse({ - required this.path, - this.name, - required this.fd, - }); - - String path; - - String? name; - - int fd; - - Object encode() { - return [ - path, - name, - fd, - ]; - } - - static FileResponse decode(Object result) { - result as List; - return FileResponse( - path: result[0]! as String, - name: result[1] as String?, - fd: result[2]! as int, - ); - } -} - -class FileTypes { - FileTypes({ - required this.mimeTypes, - required this.extensions, - }); - - List mimeTypes; - - List extensions; - - Object encode() { - return [ - mimeTypes, - extensions, - ]; - } - - static FileTypes decode(Object result) { - result as List; - return FileTypes( - mimeTypes: (result[0] as List?)!.cast(), - extensions: (result[1] as List?)!.cast(), - ); - } -} - -class _FileSelectorApiCodec extends StandardMessageCodec { - const _FileSelectorApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is FileResponse) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is FileTypes) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return FileResponse.decode(readValue(buffer)!); - case 129: - return FileTypes.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -/// An API to call to native code to select files or directories. -class FileSelectorApi { - /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - FileSelectorApi({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; - final BinaryMessenger? _binaryMessenger; - - static const MessageCodec codec = _FileSelectorApiCodec(); - - /// Opens a file dialog for loading files and returns a file path. - /// - /// Returns `null` if user cancels the operation. - Future openFileByPath(String path) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.FileSelectorApi.openFileByPath', codec, - binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send([path]) - as List?; - if (replyList == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyList.length > 1) { - throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], - ); - } else { - return (replyList[0] as int?); - } - } - -} diff --git a/ohos/automatic_camera_test/lib/main.dart b/ohos/automatic_camera_test/lib/main.dart deleted file mode 100644 index 8d4b760a..00000000 --- a/ohos/automatic_camera_test/lib/main.dart +++ /dev/null @@ -1,1087 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; - -import 'package:camera_ohos/camera_ohos.dart'; -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:video_player/video_player.dart'; - -import 'camera_controller.dart'; -import 'camera_preview.dart'; -import 'fileselector/file_selector.dart'; - -/// Camera example home widget. -class CameraExampleHome extends StatefulWidget { - /// Default Constructor - const CameraExampleHome({super.key}); - - @override - State createState() { - return _CameraExampleHomeState(); - } -} - -/// Returns a suitable camera icon for [direction]. -IconData getCameraLensIcon(CameraLensDirection direction) { - switch (direction) { - case CameraLensDirection.back: - return Icons.camera_rear; - case CameraLensDirection.front: - return Icons.camera_front; - case CameraLensDirection.external: - return Icons.camera; - } - // This enum is from a different package, so a new value could be added at - // any time. The example should keep working if that happens. - // ignore: dead_code - return Icons.camera; -} - -void _logError(String code, String? message) { - // ignore: avoid_print - print('Error: $code${message == null ? '' : '\nError Message: $message'}'); -} - -class _CameraExampleHomeState extends State - with WidgetsBindingObserver, TickerProviderStateMixin { - CameraController? controller; - XFile? imageFile; - XFile? videoFile; - VideoPlayerController? videoController; - VoidCallback? videoPlayerListener; - bool enableAudio = true; - double _minAvailableExposureOffset = 0.0; - double _maxAvailableExposureOffset = 0.0; - double _currentExposureOffset = 0.0; - late AnimationController _flashModeControlRowAnimationController; - late Animation _flashModeControlRowAnimation; - late AnimationController _exposureModeControlRowAnimationController; - late Animation _exposureModeControlRowAnimation; - late AnimationController _focusModeControlRowAnimationController; - late Animation _focusModeControlRowAnimation; - double _minAvailableZoom = 1.0; - double _maxAvailableZoom = 1.0; - double _currentScale = 1.0; - double _baseScale = 1.0; - - // Counting pointers (number of user fingers on screen) - int _pointers = 0; - - @override - void initState() { - super.initState(); - - _ambiguate(WidgetsBinding.instance)?.addObserver(this); - - _flashModeControlRowAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _flashModeControlRowAnimation = CurvedAnimation( - parent: _flashModeControlRowAnimationController, - curve: Curves.easeInCubic, - ); - _exposureModeControlRowAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _exposureModeControlRowAnimation = CurvedAnimation( - parent: _exposureModeControlRowAnimationController, - curve: Curves.easeInCubic, - ); - _focusModeControlRowAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - _focusModeControlRowAnimation = CurvedAnimation( - parent: _focusModeControlRowAnimationController, - curve: Curves.easeInCubic, - ); - } - - @override - void dispose() { - _ambiguate(WidgetsBinding.instance)?.removeObserver(this); - _flashModeControlRowAnimationController.dispose(); - _exposureModeControlRowAnimationController.dispose(); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - final CameraController? cameraController = controller; - - // App state changed before we got the chance to initialize. - if (cameraController == null || !cameraController.value.isInitialized) { - return; - } - - if (state == AppLifecycleState.inactive) { - cameraController.dispose(); - } else if (state == AppLifecycleState.resumed) { - _initializeCameraController(cameraController.description); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Camera example'), - ), - body: Column( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.black, - border: Border.all( - color: - controller != null && controller!.value.isRecordingVideo - ? Colors.redAccent - : Colors.grey, - width: 3.0, - ), - ), - child: Padding( - padding: const EdgeInsets.all(1.0), - child: Center( - child: _cameraPreviewWidget(), - ), - ), - ), - ), - _captureControlRowWidget(), - _modeControlRowWidget(), - Padding( - padding: const EdgeInsets.all(5.0), - child: Row( - children: [ - _cameraTogglesRowWidget(), - _thumbnailWidget(), - ], - ), - ), - ], - ), - ); - } - - /// Display the preview from the camera (or a message if the preview is not available). - Widget _cameraPreviewWidget() { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isInitialized) { - return const Text( - 'Tap a camera', - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.w900, - ), - ); - } else { - return Listener( - onPointerDown: (_) => _pointers++, - onPointerUp: (_) => _pointers--, - child: CameraPreview( - controller!, - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - onTapDown: (TapDownDetails details) => - onViewFinderTap(details, constraints), - ); - }), - ), - ); - } - } - - void _handleScaleStart(ScaleStartDetails details) { - _baseScale = _currentScale; - } - - Future _handleScaleUpdate(ScaleUpdateDetails details) async { - // When there are not exactly two fingers on screen don't scale - if (controller == null || _pointers != 2) { - return; - } - - _currentScale = (_baseScale * details.scale) - .clamp(_minAvailableZoom, _maxAvailableZoom); - - await CameraPlatform.instance - .setZoomLevel(controller!.cameraId, _currentScale); - } - - /// Display the thumbnail of the captured image or video. - Widget _thumbnailWidget() { - final VideoPlayerController? localVideoController = videoController; - - return Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (localVideoController == null && imageFile == null) - Container() - else - SizedBox( - width: 64.0, - height: 64.0, - child: (localVideoController == null) - ? ( - // The captured image on the web contains a network-accessible URL - // pointing to a location within the browser. It may be displayed - // either with Image.network or Image.memory after loading the image - // bytes to memory. - kIsWeb - ? Image.network(imageFile!.path) - : Image.file(File(imageFile!.path))) - : Container( - decoration: BoxDecoration( - border: Border.all(color: Colors.pink)), - child: Center( - child: AspectRatio( - aspectRatio: - localVideoController.value.aspectRatio, - child: VideoPlayer(localVideoController)), - ), - ), - ), - ], - ), - ), - ); - } - - /// Display a bar with buttons to change the flash and exposure modes - Widget _modeControlRowWidget() { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.flash_on), - color: Colors.blue, - onPressed: controller != null ? onFlashModeButtonPressed : null, - ), - // The exposure and focus mode are currently not supported on the web. - ...!kIsWeb - ? [ - IconButton( - icon: const Icon(Icons.exposure), - color: Colors.blue, - onPressed: controller != null - ? onExposureModeButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.filter_center_focus), - color: Colors.blue, - onPressed: - controller != null ? onFocusModeButtonPressed : null, - ) - ] - : [], - IconButton( - icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), - color: Colors.blue, - onPressed: controller != null ? onAudioModeButtonPressed : null, - ), - IconButton( - icon: Icon(controller?.value.isCaptureOrientationLocked ?? false - ? Icons.screen_lock_rotation - : Icons.screen_rotation), - color: Colors.blue, - onPressed: controller != null - ? onCaptureOrientationLockButtonPressed - : null, - ), - ], - ), - _flashModeControlRowWidget(), - _exposureModeControlRowWidget(), - _focusModeControlRowWidget(), - ], - ); - } - - Widget _flashModeControlRowWidget() { - return SizeTransition( - sizeFactor: _flashModeControlRowAnimation, - child: ClipRect( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.flash_off), - color: controller?.value.flashMode == FlashMode.off - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.off) - : null, - ), - IconButton( - icon: const Icon(Icons.flash_auto), - color: controller?.value.flashMode == FlashMode.auto - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.auto) - : null, - ), - IconButton( - icon: const Icon(Icons.flash_on), - color: controller?.value.flashMode == FlashMode.always - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.always) - : null, - ), - IconButton( - icon: const Icon(Icons.highlight), - color: controller?.value.flashMode == FlashMode.torch - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onSetFlashModeButtonPressed(FlashMode.torch) - : null, - ), - ], - ), - ), - ); - } - - Widget _exposureModeControlRowWidget() { - final ButtonStyle styleAuto = TextButton.styleFrom( - foregroundColor: controller?.value.exposureMode == ExposureMode.auto - ? Colors.orange - : Colors.blue, - ); - final ButtonStyle styleLocked = TextButton.styleFrom( - foregroundColor: controller?.value.exposureMode == ExposureMode.locked - ? Colors.orange - : Colors.blue, - ); - - return SizeTransition( - sizeFactor: _exposureModeControlRowAnimation, - child: ClipRect( - child: Container( - color: Colors.grey.shade50, - child: Column( - children: [ - const Center( - child: Text('Exposure Mode'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - style: styleAuto, - onPressed: controller != null - ? () => - onSetExposureModeButtonPressed(ExposureMode.auto) - : null, - onLongPress: () { - if (controller != null) { - CameraPlatform.instance - .setExposurePoint(controller!.cameraId, null); - showInSnackBar('Resetting exposure point'); - } - }, - child: const Text('AUTO'), - ), - TextButton( - style: styleLocked, - onPressed: controller != null - ? () => - onSetExposureModeButtonPressed(ExposureMode.locked) - : null, - child: const Text('LOCKED'), - ), - TextButton( - style: styleLocked, - onPressed: controller != null - ? () => controller!.setExposureOffset(0.0) - : null, - child: const Text('RESET OFFSET'), - ), - ], - ), - const Center( - child: Text('Exposure Offset'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text(_minAvailableExposureOffset.toString()), - Slider( - value: _currentExposureOffset, - min: _minAvailableExposureOffset, - max: _maxAvailableExposureOffset, - label: _currentExposureOffset.toString(), - onChanged: _minAvailableExposureOffset == - _maxAvailableExposureOffset - ? null - : setExposureOffset, - ), - Text(_maxAvailableExposureOffset.toString()), - ], - ), - ], - ), - ), - ), - ); - } - - Widget _focusModeControlRowWidget() { - final ButtonStyle styleAuto = TextButton.styleFrom( - foregroundColor: controller?.value.focusMode == FocusMode.auto - ? Colors.orange - : Colors.blue, - ); - final ButtonStyle styleLocked = TextButton.styleFrom( - foregroundColor: controller?.value.focusMode == FocusMode.locked - ? Colors.orange - : Colors.blue, - ); - - return SizeTransition( - sizeFactor: _focusModeControlRowAnimation, - child: ClipRect( - child: Container( - color: Colors.grey.shade50, - child: Column( - children: [ - const Center( - child: Text('Focus Mode'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - style: styleAuto, - onPressed: controller != null - ? () => onSetFocusModeButtonPressed(FocusMode.auto) - : null, - onLongPress: () { - if (controller != null) { - CameraPlatform.instance - .setFocusPoint(controller!.cameraId, null); - } - showInSnackBar('Resetting focus point'); - }, - child: const Text('AUTO'), - ), - TextButton( - style: styleLocked, - onPressed: controller != null - ? () => onSetFocusModeButtonPressed(FocusMode.locked) - : null, - child: const Text('LOCKED'), - ), - ], - ), - ], - ), - ), - ), - ); - } - - /// Display the control bar with buttons to take pictures and record videos. - Widget _captureControlRowWidget() { - final CameraController? cameraController = controller; - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.camera_alt), - color: Colors.blue, - onPressed: cameraController != null && - cameraController.value.isInitialized && - !cameraController.value.isRecordingVideo - ? onTakePictureButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.videocam), - color: Colors.blue, - onPressed: cameraController != null && - cameraController.value.isInitialized && - !cameraController.value.isRecordingVideo - ? onVideoRecordButtonPressed - : null, - ), - IconButton( - icon: cameraController != null && - (!cameraController.value.isRecordingVideo || - cameraController.value.isRecordingPaused) - ? const Icon(Icons.play_arrow) - : const Icon(Icons.pause), - color: Colors.blue, - onPressed: cameraController != null && - cameraController.value.isInitialized && - cameraController.value.isRecordingVideo - ? (cameraController.value.isRecordingPaused) - ? onResumeButtonPressed - : onPauseButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.stop), - color: Colors.red, - onPressed: cameraController != null && - cameraController.value.isInitialized && - cameraController.value.isRecordingVideo - ? onStopButtonPressed - : null, - ), - IconButton( - icon: const Icon(Icons.pause_presentation), - color: - cameraController != null && cameraController.value.isPreviewPaused - ? Colors.red - : Colors.blue, - onPressed: - cameraController == null ? null : onPausePreviewButtonPressed, - ), - ], - ); - } - - /// Display a row of toggle to select the camera (or a message if no camera is available). - Widget _cameraTogglesRowWidget() { - final List toggles = []; - - void onChanged(CameraDescription? description) { - if (description == null) { - return; - } - - onNewCameraSelected(description); - } - - if (_cameras.isEmpty) { - _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async { - showInSnackBar('No camera found.'); - }); - return const Text('None'); - } else { - for (final CameraDescription cameraDescription in _cameras) { - toggles.add( - SizedBox( - width: 90.0, - child: RadioListTile( - title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), - groupValue: controller?.description, - value: cameraDescription, - onChanged: onChanged, - ), - ), - ); - } - } - - return Row(children: toggles); - } - - String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); - - void showInSnackBar(String message) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(message))); - } - - void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { - if (controller == null) { - return; - } - - final CameraController cameraController = controller!; - - final Point point = Point( - details.localPosition.dx / constraints.maxWidth, - details.localPosition.dy / constraints.maxHeight, - ); - CameraPlatform.instance.setExposurePoint(cameraController.cameraId, point); - CameraPlatform.instance.setFocusPoint(cameraController.cameraId, point); - } - - Future onNewCameraSelected(CameraDescription cameraDescription) async { - if (controller != null) { - return controller!.setDescription(cameraDescription); - } else { - return _initializeCameraController(cameraDescription); - } - } - - Future _initializeCameraController( - CameraDescription cameraDescription) async { - final CameraController cameraController = CameraController( - cameraDescription, - kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, - enableAudio: enableAudio, - imageFormatGroup: ImageFormatGroup.jpeg, - ); - - controller = cameraController; - - // If the controller is updated then update the UI. - cameraController.addListener(() { - if (mounted) { - setState(() {}); - } - }); - - try { - await cameraController.initialize(); - await Future.wait(>[ - // The exposure mode is currently not supported on the web. - ...!kIsWeb - ? >[ - CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId) - .then( - (double value) => _minAvailableExposureOffset = value), - CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId) - .then((double value) => _maxAvailableExposureOffset = value) - ] - : >[], - CameraPlatform.instance - .getMaxZoomLevel(cameraController.cameraId) - .then((double value) => _maxAvailableZoom = value), - CameraPlatform.instance - .getMinZoomLevel(cameraController.cameraId) - .then((double value) => _minAvailableZoom = value), - ]); - } on CameraException catch (e) { - switch (e.code) { - case 'CameraAccessDenied': - showInSnackBar('You have denied camera access.'); - break; - case 'CameraAccessDeniedWithoutPrompt': - // iOS only - showInSnackBar('Please go to Settings app to enable camera access.'); - break; - case 'CameraAccessRestricted': - // iOS only - showInSnackBar('Camera access is restricted.'); - break; - case 'AudioAccessDenied': - showInSnackBar('You have denied audio access.'); - break; - case 'AudioAccessDeniedWithoutPrompt': - // iOS only - showInSnackBar('Please go to Settings app to enable audio access.'); - break; - case 'AudioAccessRestricted': - // iOS only - showInSnackBar('Audio access is restricted.'); - break; - case 'cameraPermission': - // Android & web only - showInSnackBar('Unknown permission error.'); - break; - default: - _showCameraException(e); - break; - } - } - - if (mounted) { - setState(() {}); - } - } - - void onTakePictureButtonPressed() { - takePicture().then((XFile? file) { - if (mounted) { - setState(() { - imageFile = file; - videoController?.dispose(); - videoController = null; - }); - if (file != null) { - showInSnackBar('Picture saved to ${file.path}'); - } - } - }); - } - - void onFlashModeButtonPressed() { - if (_flashModeControlRowAnimationController.value == 1) { - _flashModeControlRowAnimationController.reverse(); - } else { - _flashModeControlRowAnimationController.forward(); - _exposureModeControlRowAnimationController.reverse(); - _focusModeControlRowAnimationController.reverse(); - } - } - - void onExposureModeButtonPressed() { - if (_exposureModeControlRowAnimationController.value == 1) { - _exposureModeControlRowAnimationController.reverse(); - } else { - _exposureModeControlRowAnimationController.forward(); - _flashModeControlRowAnimationController.reverse(); - _focusModeControlRowAnimationController.reverse(); - } - } - - void onFocusModeButtonPressed() { - if (_focusModeControlRowAnimationController.value == 1) { - _focusModeControlRowAnimationController.reverse(); - } else { - _focusModeControlRowAnimationController.forward(); - _flashModeControlRowAnimationController.reverse(); - _exposureModeControlRowAnimationController.reverse(); - } - } - - void onAudioModeButtonPressed() { - enableAudio = !enableAudio; - if (controller != null) { - onNewCameraSelected(controller!.description); - } - } - - Future onCaptureOrientationLockButtonPressed() async { - try { - if (controller != null) { - final CameraController cameraController = controller!; - if (cameraController.value.isCaptureOrientationLocked) { - await cameraController.unlockCaptureOrientation(); - showInSnackBar('Capture orientation unlocked'); - } else { - await cameraController.lockCaptureOrientation(); - showInSnackBar( - 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); - } - } - } on CameraException catch (e) { - _showCameraException(e); - } - } - - void onSetFlashModeButtonPressed(FlashMode mode) { - setFlashMode(mode).then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); - }); - } - - void onSetExposureModeButtonPressed(ExposureMode mode) { - setExposureMode(mode).then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); - }); - } - - void onSetFocusModeButtonPressed(FocusMode mode) { - setFocusMode(mode).then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); - }); - } - - void onVideoRecordButtonPressed() { - startVideoRecording().then((_) { - if (mounted) { - setState(() {}); - } - }); - } - - void onStopButtonPressed() { - stopVideoRecording().then((XFile? file) { - if (mounted) { - setState(() {}); - } - if (file != null) { - showInSnackBar('Video recorded to ${file.path}'); - videoFile = file; - _startVideoPlayer(); - } - }); - } - - Future onPausePreviewButtonPressed() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return; - } - - if (cameraController.value.isPreviewPaused) { - await cameraController.resumePreview(); - } else { - await cameraController.pausePreview(); - } - - if (mounted) { - setState(() {}); - } - } - - void onPauseButtonPressed() { - pauseVideoRecording().then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Video recording paused'); - }); - } - - void onResumeButtonPressed() { - resumeVideoRecording().then((_) { - if (mounted) { - setState(() {}); - } - showInSnackBar('Video recording resumed'); - }); - } - - Future startVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return; - } - - if (cameraController.value.isRecordingVideo) { - // A recording is already started, do nothing. - return; - } - - try { - await cameraController.startVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - return; - } - } - - Future stopVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isRecordingVideo) { - return null; - } - - try { - return cameraController.stopVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - return null; - } - } - - Future pauseVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isRecordingVideo) { - return; - } - - try { - await cameraController.pauseVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future resumeVideoRecording() async { - final CameraController? cameraController = controller; - - if (cameraController == null || !cameraController.value.isRecordingVideo) { - return; - } - - try { - await cameraController.resumeVideoRecording(); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setFlashMode(FlashMode mode) async { - if (controller == null) { - return; - } - - try { - await controller!.setFlashMode(mode); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setExposureMode(ExposureMode mode) async { - if (controller == null) { - return; - } - - try { - await controller!.setExposureMode(mode); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setExposureOffset(double offset) async { - if (controller == null) { - return; - } - - setState(() { - _currentExposureOffset = offset; - }); - try { - offset = await controller!.setExposureOffset(offset); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future setFocusMode(FocusMode mode) async { - if (controller == null) { - return; - } - - try { - await controller!.setFocusMode(mode); - } on CameraException catch (e) { - _showCameraException(e); - rethrow; - } - } - - Future _startVideoPlayer() async { - if (videoFile == null) { - return; - } - final VideoPlayerController vController; - if (Platform.operatingSystem == 'ohos') { - final FileSelector instance = FileSelector(); - int? fileFd = await instance.openFileByPath(videoFile!.path); - vController = VideoPlayerController.fileFd(fileFd!); - } else { - vController = kIsWeb? VideoPlayerController.network(videoFile!.path) - : VideoPlayerController.file(File(videoFile!.path)); - } - videoPlayerListener = () { - if (videoController != null) { - // Refreshing the state to update video player with the correct ratio. - if (mounted) { - setState(() {}); - } - videoController!.removeListener(videoPlayerListener!); - } - }; - vController.addListener(videoPlayerListener!); - await vController.setLooping(true); - await vController.initialize(); - await videoController?.dispose(); - if (mounted) { - setState(() { - imageFile = null; - videoController = vController; - }); - } - await vController.play(); - print("========_startVideoPlayer end: "+videoFile!.path); - } - - Future takePicture() async { - final CameraController? cameraController = controller; - if (cameraController == null || !cameraController.value.isInitialized) { - showInSnackBar('Error: select a camera first.'); - return null; - } - - if (cameraController.value.isTakingPicture) { - // A capture is already pending, do nothing. - return null; - } - - try { - final XFile file = await cameraController.takePicture(); - return file; - } on CameraException catch (e) { - _showCameraException(e); - return null; - } - } - - void _showCameraException(CameraException e) { - _logError(e.code, e.description); - showInSnackBar('Error: ${e.code}\n${e.description}'); - } -} - -/// CameraApp is the Main Application. -class CameraApp extends StatelessWidget { - /// Default Constructor - const CameraApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: CameraExampleHome(), - ); - } -} - -List _cameras = []; - -Future main() async { - // Fetch the available cameras before initializing the app. - try { - WidgetsFlutterBinding.ensureInitialized(); - _cameras = await CameraPlatform.instance.availableCameras(); - } on CameraException catch (e) { - _logError(e.code, e.description); - } - runApp(const CameraApp()); -} - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; -- Gitee From e6fd801deacc12e4fb296d0d4bf7331a05b680cf Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:17:54 +0800 Subject: [PATCH 07/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4camera=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- ohos/automatic_camera_test/ohos/.gitignore | 19 ++ .../ohos/AppScope/app.json5 | 10 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../ohos/build-profile.json5 | 41 ++++ .../ohos/entry/.gitignore | 7 + .../ohos/entry/build-profile.json5 | 29 +++ .../ohos/entry/hvigorfile.ts | 17 ++ .../main/ets/entryability/EntryAbility.ets | 27 +++ .../main/ets/fileselector/FileSelector.ets | 82 ++++++++ .../fileselector/FileSelectorOhosPlugin.ets | 56 +++++ .../fileselector/GeneratedFileSelectorApi.ets | 194 ++++++++++++++++++ .../ohos/entry/src/main/ets/pages/Index.ets | 38 ++++ .../ets/plugins/GeneratedPluginRegistrant.ets | 32 +++ .../main/resources/base/element/color.json | 8 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../src/ohosTest/ets/test/Ability.test.ets | 50 +++++ .../resources/base/element/color.json | 8 + .../ohosTest/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../ohos/hvigor/hvigor-config.json5 | 23 +++ ohos/automatic_camera_test/ohos/hvigorfile.ts | 21 ++ 20 files changed, 662 insertions(+) create mode 100644 ohos/automatic_camera_test/ohos/.gitignore create mode 100644 ohos/automatic_camera_test/ohos/AppScope/app.json5 create mode 100644 ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 ohos/automatic_camera_test/ohos/build-profile.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/.gitignore create mode 100644 ohos/automatic_camera_test/ohos/entry/build-profile.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/hvigorfile.ts create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelector.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/pages/Index.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/base/media/icon.png create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/media/icon.png create mode 100644 ohos/automatic_camera_test/ohos/hvigor/hvigor-config.json5 create mode 100644 ohos/automatic_camera_test/ohos/hvigorfile.ts diff --git a/ohos/automatic_camera_test/ohos/.gitignore b/ohos/automatic_camera_test/ohos/.gitignore new file mode 100644 index 00000000..ee3ce118 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/.gitignore @@ -0,0 +1,19 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +*.har +**/BuildProfile.ets +**/oh-package-lock.json5 + +**/src/main/resources/rawfile/flutter_assets/ +**/libs/arm64-v8a/libapp.so +**/libs/arm64-v8a/libflutter.so +**/libs/arm64-v8a/libvmservice_snapshot.so \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/AppScope/app.json5 b/ohos/automatic_camera_test/ohos/AppScope/app.json5 new file mode 100644 index 00000000..c144b41c --- /dev/null +++ b/ohos/automatic_camera_test/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "io.flutter.plugins.example", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png b/ohos/automatic_camera_test/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 zcmX|G1ymHk)?T_}Vd;>R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y): Promise { + try { + let file = await fs.open(path); + result.success(file.fd); + } catch (err) { + Log.e(TAG, 'open file failed with err: ' + err); + result.error(new Error("Failed to read file")); + } + } + + static getCodec(): MessageCodec { + return FileSelectorApiCodec.INSTANCE; + } + + setup(binaryMessenger: BinaryMessenger, abilityPluginBinding: AbilityPluginBinding): void { + let api = this; + { + this.binding = abilityPluginBinding; + const channel: BasicMessageChannel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.FileSelectorApi.openFileByPath", FileSelector.getCodec()); + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + Log.d(TAG, 'onMessage reply:' + reply) + const wrapped: Array = new Array(); + const args: Array = msg as Array; + const path = args[0] as string; + const resultCallback: Result = new ResultBuilder((result: number): void => { + wrapped.push(result); + reply.reply(wrapped); + },(error: Error): void => { + const wrappedError: ArrayList = msg.wrapError(error); + reply.reply(wrappedError); + }) + api.openFileByPath(path, resultCallback); + } + }); + } + } +} + +class ResultBuilder{ + success : (result: number)=>void + error: (error: Error) =>void + + constructor(success:ESObject , error:ESObject) { + this.success = success + this.error = error + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets new file mode 100644 index 00000000..2b3e4179 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import AbilityAware from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware'; +import { + AbilityPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding'; +import { + FlutterPlugin, + FlutterPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; +import { FileSelector } from './FileSelector' + +const TAG = "FileSelectorOhosPlugin" + +export default class FileSelectorOhosPlugin implements FlutterPlugin, AbilityAware { + + private pluginBinding: FlutterPluginBinding | null = null; + private fileSelectorApi: FileSelector | null = null; + + getUniqueClassName(): string { + return "FileSelectorOhosPlugin" + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + this.fileSelectorApi = new FileSelector(binding); + if (this.pluginBinding != null) { + this.fileSelectorApi.setup(this.pluginBinding.getBinaryMessenger(), binding); + } + } + + onDetachedFromAbility(): void { + this.fileSelectorApi = null; + } + + onAttachedToEngine(binding: FlutterPluginBinding): void { + console.debug(TAG, 'onAttachedToEngine file selector ') + this.pluginBinding = binding; + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + this.pluginBinding = null; + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets new file mode 100644 index 00000000..2d1f8cbe --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets @@ -0,0 +1,194 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { ByteBuffer } from '@ohos/flutter_ohos/src/main/ets/util/ByteBuffer'; +import StandardMessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec'; +import Log from '@ohos/flutter_ohos/src/main/ets/util/Log'; + +const TAG = "GeneratedFileSelectorApi"; + +class FlutterError extends Error { + /** The error code. */ + public code: string; + + /** The error details. Must be a datatype supported by the api codec. */ + public details: ESObject; + + constructor(code: string, message: string, details: ESObject) { + super(message); + this.code = code; + this.details = details; + } +} + +export function wrapError(exception: Error): Array { + let errorList: Array = new Array(); + if (exception instanceof FlutterError) { + let error = exception as FlutterError; + errorList.push(error.code); + errorList.push(error.message); + errorList.push(error.details); + } else { + errorList.push(exception.toString()); + errorList.push(exception.name); + errorList.push( + "Cause: " + exception.message + ", Stacktrace: " + exception.stack); + } + return errorList; +} + +export class FileResponse { + private path: string; + + public getPath(): string { + return this.path; + } + + public setPath(setterArg: string): void { + if (setterArg == null) { + throw new Error('Nonnull field \'path\' is null.'); + } + this.path = setterArg; + } + + private name: string; + + public getName(): string { + return this.name; + } + + public setName(setterArg: string): void { + this.name = setterArg; + } + + private fd: number; + + public getFd(): number { + return this.fd; + } + + public setFd(setterArg: number): void { + if (setterArg == null) { + throw new Error("Nonnull field \"fd\" is null."); + } + this.fd = setterArg; + } + + constructor(path: string, name: string, fd: number) { + this.path = path; + this.name = name; + this.fd = fd; + } + + toList(): Array { + let toListResult: Array = new Array(); + toListResult.push(this.path); + toListResult.push(this.name); + toListResult.push(this.fd); + return toListResult; + } + + static fromList(list: Array): FileResponse { + let path: ESObject = list[0]; + let name: ESObject = list[1]; + let fd: ESObject = list[2]; + let response = new FileResponse(path, name, fd); + return response; + } +} + +export class FileTypes { + mimeTypes: Array = []; + + getMimeTypes(): Array { + return this.mimeTypes; + } + + setMimeTypes(setterArg: Array | null): void { + if (setterArg == null) { + throw new Error("Nonnull field \"mimeTypes\" is null."); + } + this.mimeTypes = setterArg; + } + + extensions: Array = []; + + getExtensions(): Array { + return this.extensions; + } + + setExtensions(setterArg: Array): void { + if (setterArg == null) { + throw new Error("Nonnull field \"extensions\" is null."); + } + this.extensions = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + FileTypes() {} + + toList(): Array { + let toListResult: Array = new Array(); + toListResult.push(this.mimeTypes); + toListResult.push(this.extensions); + return toListResult; + } + + static fromList(list: Array): FileTypes { + let pigeonResult = new FileTypes(); + let mimeTypes: ESObject = list[0]; + pigeonResult.setMimeTypes(mimeTypes as Array); + let extensions: ESObject = list[1]; + pigeonResult.setExtensions(extensions as Array); + return pigeonResult; + } +} + + +export interface Result { + success(result: T): void; + error(error: Error): void; +} + +export class FileSelectorApiCodec extends StandardMessageCodec { + public static INSTANCE = new FileSelectorApiCodec(); + + readValueOfType(type: number, buffer: ByteBuffer): ESObject { + switch (type) { + case 128: + let res0 = FileResponse.fromList(super.readValue(buffer) as Array); + return res0; + case 129: + let vur: ESObject = super.readValue(buffer) + let res1 = FileTypes.fromList(vur as Array); + return res1; + default: + let res2: ESObject = super.readValueOfType(type, buffer); + return res2; + } + } + + writeValue(stream: ByteBuffer, value: ESObject): ESObject { + if (value instanceof FileResponse) { + stream.writeInt8(128); + return this.writeValue(stream, (value as FileResponse).toList()); + } else if (value instanceof FileTypes) { + stream.writeInt8(129); + return this.writeValue(stream, (value as FileTypes).toList()); + } else { + return super.writeValue(stream, value); + } + } + } diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/pages/Index.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 00000000..5f09a903 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,38 @@ +/** +* Copyright (c) 2024 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import common from '@ohos.app.ability.common'; +import { FlutterPage } from '@ohos/flutter_ohos' + +let storage = LocalStorage.getShared() +const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' + +@Entry(storage) +@Component +struct Index { + private context = getContext(this) as common.UIAbilityContext + @LocalStorageLink('viewId') viewId: string = ""; + + build() { + Column() { + FlutterPage({ viewId: this.viewId }) + } + } + + onBackPress(): boolean { + this.context.eventHub.emit(EVENT_BACK_PRESS) + return true + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets new file mode 100644 index 00000000..82e171e4 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets @@ -0,0 +1,32 @@ +import { FlutterEngine, Log } from '@ohos/flutter_ohos'; +import CameraPlugin from 'camera_ohos'; +import IntegrationTestPlugin from 'integration_test'; +import PathProviderPlugin from 'path_provider_ohos'; +import VideoPlayerPlugin from 'video_player_ohos'; + +/** + * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Ohos platform. + */ + +const TAG = "GeneratedPluginRegistrant"; + +export class GeneratedPluginRegistrant { + + static registerWith(flutterEngine: FlutterEngine) { + try { + flutterEngine.getPlugins()?.add(new CameraPlugin()); + flutterEngine.getPlugins()?.add(new IntegrationTestPlugin()); + flutterEngine.getPlugins()?.add(new PathProviderPlugin()); + flutterEngine.getPlugins()?.add(new VideoPlayerPlugin()); + } catch (e) { + Log.e( + TAG, + "Tried to register plugins with FlutterEngine (" + + flutterEngine + + ") failed."); + Log.e(TAG, "Received exception while registering", e); + } + } +} diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 00000000..3c712962 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/media/icon.png b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 Date: Wed, 13 Nov 2024 19:18:53 +0800 Subject: [PATCH 08/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4camera=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../resources/base/element/string.json | 8 ++ .../ohos/entry/oh-package.json5 | 14 ++++ .../ohos/entry/src/main/module.json5 | 82 +++++++++++++++++++ .../main/resources/base/element/string.json | 20 +++++ .../resources/base/profile/main_pages.json | 5 ++ .../main/resources/en_US/element/string.json | 20 +++++ .../main/resources/zh_CN/element/string.json | 16 ++++ .../entry/src/ohosTest/ets/test/List.test.ets | 20 +++++ .../ohosTest/ets/testability/TestAbility.ets | 63 ++++++++++++++ .../ohosTest/ets/testability/pages/Index.ets | 49 +++++++++++ .../ets/testrunner/OpenHarmonyTestRunner.ts | 64 +++++++++++++++ .../ohos/entry/src/ohosTest/module.json5 | 51 ++++++++++++ .../resources/base/element/string.json | 16 ++++ .../resources/base/profile/test_pages.json | 5 ++ .../ohos/oh-package.json5 | 23 ++++++ 15 files changed, 456 insertions(+) create mode 100644 ohos/automatic_camera_test/ohos/AppScope/resources/base/element/string.json create mode 100644 ohos/automatic_camera_test/ohos/entry/oh-package.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/module.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/string.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/base/profile/main_pages.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/en_US/element/string.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/main/resources/zh_CN/element/string.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/module.json5 create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/string.json create mode 100644 ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json create mode 100644 ohos/automatic_camera_test/ohos/oh-package.json5 diff --git a/ohos/automatic_camera_test/ohos/AppScope/resources/base/element/string.json b/ohos/automatic_camera_test/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 00000000..810f4a36 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "example" + } + ] +} diff --git a/ohos/automatic_camera_test/ohos/entry/oh-package.json5 b/ohos/automatic_camera_test/ohos/entry/oh-package.json5 new file mode 100644 index 00000000..dcb7708a --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "integration_test": "file:../har/integration_test.har", + "camera_ohos": "file:../har/camera_ohos.har", + "path_provider_ohos": "file:../har/path_provider_ohos.har", + "video_player_ohos": "file:../har/video_player_ohos.har" + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/module.json5 b/ohos/automatic_camera_test/ohos/entry/src/main/module.json5 new file mode 100644 index 00000000..9757bb40 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/module.json5 @@ -0,0 +1,82 @@ +/** +* Copyright (c) 2024 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.CAMERA", + "reason": "$string:reason", + "usedScene": { + "abilities": [ + "FormAbility" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:reason", + "usedScene": { + "abilities": [ + "FormAbility" + ], + "when": "inuse" + } + }, + ] + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/string.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000..aa3c490a --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "example" + }, + { + "name": "reason", + "value": "example" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/profile/main_pages.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 00000000..1898d94f --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/en_US/element/string.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 00000000..aa3c490a --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "example" + }, + { + "name": "reason", + "value": "example" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/main/resources/zh_CN/element/string.json b/ohos/automatic_camera_test/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 00000000..601e2b5a --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "example" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/List.test.ets b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 00000000..f4140030 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 00000000..4ca645e6 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; + +export default class TestAbility extends UIAbility { + onCreate(want, launchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 00000000..cef0447c --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 00000000..1def08f2 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/module.json5 b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 00000000..fab77ce2 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/string.json b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 00000000..65d8fa5a --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 00000000..b7e7343c --- /dev/null +++ b/ohos/automatic_camera_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/ohos/automatic_camera_test/ohos/oh-package.json5 b/ohos/automatic_camera_test/ohos/oh-package.json5 new file mode 100644 index 00000000..4a28d3a1 --- /dev/null +++ b/ohos/automatic_camera_test/ohos/oh-package.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "5.0.0", + "name": "apptemplate", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + }, + "devDependencies": { + "@ohos/hypium": "1.0.6" + }, + "overrides": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "integration_test": "file:./har/integration_test.har", + "camera_ohos": "file:./har/camera_ohos.har", + "path_provider_ohos": "file:./har/path_provider_ohos.har", + "video_player_ohos": "file:./har/video_player_ohos.har", + "@ohos/flutter_module": "file:./entry" + } +} \ No newline at end of file -- Gitee From 5a44cd2b9f26ef81b8f6d3e20cff0e72cbba971a Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:23:30 +0800 Subject: [PATCH 09/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4camera=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- ohos/automatic_camera_test/.metadata | 43 + .../.pluginToolsConfig.yaml | 17 + ohos/automatic_camera_test/README.md | 16 + .../analysis_options.yaml | 43 + .../integration_test/camera_test.dart | 489 ++++++++ .../lib/camera_controller.dart | 574 +++++++++ .../lib/camera_preview.dart | 87 ++ .../lib/fileselector/file_selector.dart | 33 + .../lib/fileselector/file_selector_api.dart | 142 +++ ohos/automatic_camera_test/lib/main.dart | 1087 +++++++++++++++++ ohos/automatic_camera_test/pubspec.yaml | 60 + .../test/widget_test.dart | 45 + .../test_driver/integration_test.dart | 66 + 13 files changed, 2702 insertions(+) create mode 100644 ohos/automatic_camera_test/.metadata create mode 100644 ohos/automatic_camera_test/.pluginToolsConfig.yaml create mode 100644 ohos/automatic_camera_test/README.md create mode 100644 ohos/automatic_camera_test/analysis_options.yaml create mode 100644 ohos/automatic_camera_test/integration_test/camera_test.dart create mode 100644 ohos/automatic_camera_test/lib/camera_controller.dart create mode 100644 ohos/automatic_camera_test/lib/camera_preview.dart create mode 100644 ohos/automatic_camera_test/lib/fileselector/file_selector.dart create mode 100644 ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart create mode 100644 ohos/automatic_camera_test/lib/main.dart create mode 100644 ohos/automatic_camera_test/pubspec.yaml create mode 100644 ohos/automatic_camera_test/test/widget_test.dart create mode 100644 ohos/automatic_camera_test/test_driver/integration_test.dart diff --git a/ohos/automatic_camera_test/.metadata b/ohos/automatic_camera_test/.metadata new file mode 100644 index 00000000..b6470aff --- /dev/null +++ b/ohos/automatic_camera_test/.metadata @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + channel: dev + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + base_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + - platform: ohos + create_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + base_revision: e17198462e2eea076848cc9ddbfaea4d0f5228f3 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/ohos/automatic_camera_test/.pluginToolsConfig.yaml b/ohos/automatic_camera_test/.pluginToolsConfig.yaml new file mode 100644 index 00000000..aa1cfb81 --- /dev/null +++ b/ohos/automatic_camera_test/.pluginToolsConfig.yaml @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +buildFlags: + _pluginToolsConfigGlobalKey: + - "--no-tree-shake-icons" + - "--dart-define=buildmode=testing" diff --git a/ohos/automatic_camera_test/README.md b/ohos/automatic_camera_test/README.md new file mode 100644 index 00000000..2b3fce4c --- /dev/null +++ b/ohos/automatic_camera_test/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/ohos/automatic_camera_test/analysis_options.yaml b/ohos/automatic_camera_test/analysis_options.yaml new file mode 100644 index 00000000..c7076b7e --- /dev/null +++ b/ohos/automatic_camera_test/analysis_options.yaml @@ -0,0 +1,43 @@ +# Copyright (c) 2024 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/ohos/automatic_camera_test/integration_test/camera_test.dart b/ohos/automatic_camera_test/integration_test/camera_test.dart new file mode 100644 index 00000000..7870f8c6 --- /dev/null +++ b/ohos/automatic_camera_test/integration_test/camera_test.dart @@ -0,0 +1,489 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; +import 'dart:ui' as ui show Image; + +import 'package:camera_ohos/camera_ohos.dart'; +import 'package:automatic_camera_test/camera_controller.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:video_player/video_player.dart'; + +void main() { + late Directory testDir; + + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() async { + CameraPlatform.instance = OhosCamera(); + final Directory extDir = await getTemporaryDirectory(); + testDir = await Directory('${extDir.path}/integration_test').create(recursive: true); + }); + + tearDownAll(() async { + await testDir.delete(recursive: true); + }); + + final Map presetExpectedSizes = + { + ResolutionPreset.low: const Size(240, 320), + ResolutionPreset.medium: const Size(480, 720), + ResolutionPreset.high: const Size(720, 1280), + ResolutionPreset.veryHigh: const Size(1080, 1920), + ResolutionPreset.ultraHigh: const Size(2160, 3840), + // Don't bother checking for max here since it could be anything. + }; + + /// Verify that [actual] has dimensions that are at least as large as + /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns + /// whether the dimensions exactly match. + bool assertExpectedDimensions(Size expectedSize, Size actual) { + expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); + expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); + return actual.shortestSide == expectedSize.shortestSide && + actual.longestSide == expectedSize.longestSide; + } + + // This tests that the capture is no bigger than the preset, since we have + // automatic code to fall back to smaller sizes when we need to. Returns + // whether the image is exactly the desired resolution. + Future testCaptureImageResolution( + CameraController controller, ResolutionPreset preset) async { + final Size expectedSize = presetExpectedSizes[preset]!; + + // Take Picture + final XFile file = await controller.takePicture(); + + // Load picture + final File fileImage = File(file.path); + final Image image = (await decodeImageFromList(fileImage.readAsBytesSync())) as Image; + + // Verify image dimensions are as expected + expect(image, isNotNull); + return assertExpectedDimensions( + expectedSize, Size(image.height as double, image.width as double)); + } + + testWidgets( + 'Capture specific image resolutions', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + for (final CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (final MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); + await controller.initialize(); + final bool presetExactlySupported = + await testCaptureImageResolution(controller, preset.key); + assert(!(!previousPresetExactlySupported && presetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); + previousPresetExactlySupported = presetExactlySupported; + await controller.dispose(); + } + } + }, + // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. + skip: true, + ); + + // This tests that the capture is no bigger than the preset, since we have + // automatic code to fall back to smaller sizes when we need to. Returns + // whether the image is exactly the desired resolution. + Future testCaptureVideoResolution( + CameraController controller, ResolutionPreset preset) async { + final Size expectedSize = presetExpectedSizes[preset]!; + + // Take Video + await controller.startVideoRecording(); + sleep(const Duration(milliseconds: 300)); + final XFile file = await controller.stopVideoRecording(); + + // Load video metadata + final File videoFile = File(file.path); + final VideoPlayerController videoController = + VideoPlayerController.file(videoFile); + await videoController.initialize(); + final Size video = videoController.value.size; + + // Verify image dimensions are as expected + expect(video, isNotNull); + return assertExpectedDimensions( + expectedSize, Size(video.height, video.width)); + } + + testWidgets( + 'Capture specific video resolutions', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + for (final CameraDescription cameraDescription in cameras) { + bool previousPresetExactlySupported = true; + for (final MapEntry preset + in presetExpectedSizes.entries) { + final CameraController controller = + CameraController(cameraDescription, preset.key); + await controller.initialize(); + await controller.prepareForVideoRecording(); + final bool presetExactlySupported = + await testCaptureVideoResolution(controller, preset.key); + assert(!(!previousPresetExactlySupported && presetExactlySupported), + 'The camera took higher resolution pictures at a lower resolution.'); + previousPresetExactlySupported = presetExactlySupported; + await controller.dispose(); + } + } + }, + // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. + skip: true, + ); + + testWidgets('Pause and resume video recording', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.prepareForVideoRecording(); + + int startPause; + int timePaused = 0; + + await controller.startVideoRecording(); + final int recordingStart = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + + await controller.pauseVideoRecording(); + startPause = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + await controller.resumeVideoRecording(); + timePaused += DateTime.now().millisecondsSinceEpoch - startPause; + + sleep(const Duration(milliseconds: 500)); + + await controller.pauseVideoRecording(); + startPause = DateTime.now().millisecondsSinceEpoch; + sleep(const Duration(milliseconds: 500)); + await controller.resumeVideoRecording(); + timePaused += DateTime.now().millisecondsSinceEpoch - startPause; + + sleep(const Duration(milliseconds: 500)); + + final XFile file = await controller.stopVideoRecording(); + + final int recordingTime = + DateTime.now().millisecondsSinceEpoch - recordingStart; + + final File videoFile = File(file.path); + final VideoPlayerController videoController = VideoPlayerController.file( + videoFile, + ); + await videoController.initialize(); + final int duration = videoController.value.duration.inMilliseconds; + await videoController.dispose(); + + print("====Pause and resume video recording-" + "${(duration < (recordingTime - timePaused)) == true ? "success" : "failed"}:"); + expect(duration, lessThan(recordingTime - timePaused)); + try { + controller.dispose(); + } on Exception catch (e) { + print(""); + } + }); + + testWidgets('Set description while recording', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.prepareForVideoRecording(); + + await controller.startVideoRecording(); + + // SDK < 26 will throw a platform error when trying to switch and keep the same camera + // we accept either outcome here, while the native unit tests check the outcome based on the current Android SDK + bool failed = false; + try { + await controller.setDescription(cameras[1]); + } catch (err) { + // expect(err, isA()); + // expect( + // (err as PlatformException).message, + // equals( + // 'Device does not support switching the camera while recording')); + failed = true; + } + + if (failed) { + // cameras did not switch + print("====Set description while recording-" + "${(controller.description==cameras[0]) == true ? "success" : "failed"}:"); + // expect(controller.description, cameras[0]); + } else { + // cameras switched + print("====Set description while recording-" + "${(controller.description==cameras[1]) == true ? "success" : "failed"}:"); + // expect(controller.description, cameras[1]); + } + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + }); + + testWidgets('Set description', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.setDescription(cameras[1]); + + print("====Set description-" + "${(controller.description==cameras[1]) == true ? "success" : "failed"}:"); + expect(controller.description, cameras[1]); + + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + }); + + testWidgets( + 'image streaming', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + bool isDetecting = false; + + await controller.startImageStream((CameraImageData image) { + if (isDetecting) { + return; + } + + isDetecting = true; + + expectLater(image, isNotNull).whenComplete(() => isDetecting = false); + }); + + print("====image streaming-" + "${controller.value.isStreamingImages == true ? "success" : "failed"}:"); + expect(controller.value.isStreamingImages, true); + + sleep(const Duration(milliseconds: 500)); + + await controller.stopImageStream(); + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + }, + ); + + testWidgets( + 'recording with image stream', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + bool isDetecting = false; + + await controller.startVideoRecording( + streamCallback: (CameraImageData image) { + if (isDetecting) { + return; + } + + isDetecting = true; + + expectLater(image, isNotNull); + }); + + var flag = controller.value.isStreamingImages == true; + expect(controller.value.isStreamingImages, true); + + await Future.delayed(const Duration(seconds: 2)); + + await controller.stopVideoRecording(); + try { + await controller.dispose(); + } on Exception catch (e) { + print(""); + } + print("====recording with image stream-" + "${(controller.value.isStreamingImages == false) && flag == true ? "success" : "failed"}:"); + expect(controller.value.isStreamingImages, false); + }, + ); + + testWidgets( + 'takePicture', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + await controller.initialize(); + + // Take Picture + final XFile file = await controller.takePicture(); + // Load picture + final File fileImage = File(file.path); + + final ui.Image image = await decodeImageFromList(fileImage.readAsBytesSync()); + + print("====takePicture-" + "${(image != null) == true ? "success" : "failed"}:"); + try { + controller.dispose(); + } on Exception catch(e) { + print(""); + } + }, + ); + + testWidgets( + 'previewerMode', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + + final Widget widget = controller.buildPreview(); + + print("====previewerMode - build previewer :" + "${(widget as Texture).textureId == controller.cameraId ? "success" : "failed"}:"); + + await controller.pausePreview(); + + print("====previewerMode - paused previewer: " + "${(controller.value.isPreviewPaused) ? "success" : "failed"}:"); + + await controller.resumePreview(); + print("====previewerMode - resume previewer: " + "${(!controller.value.isPreviewPaused) ? "success" : "failed"}:"); + + try { + controller.dispose(); + } on Exception catch(e) { + print(""); + } + }, + ); + + testWidgets( + 'ExposureMode', + (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.setExposureMode(ExposureMode.auto); + + print("====ExposureMode - set ExposureMode :" + "${controller.value.exposureMode == ExposureMode.auto ? "success" : "failed"}:"); + + final offset = await controller.setExposureOffset(1.0); + + print("set offset is ${offset}"); + print("====ExposureMode - set ExposureOffset : " + "${(offset == 2.0) ? "success" : "failed"}:"); + + try { + controller.dispose(); + } on Exception catch(e) { + print(""); + } + }, + ); +} diff --git a/ohos/automatic_camera_test/lib/camera_controller.dart b/ohos/automatic_camera_test/lib/camera_controller.dart new file mode 100644 index 00000000..9ab06140 --- /dev/null +++ b/ohos/automatic_camera_test/lib/camera_controller.dart @@ -0,0 +1,574 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:collection'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// The state of a [CameraController]. +class CameraValue { + /// Creates a new camera controller state. + const CameraValue({ + required this.isInitialized, + this.previewSize, + required this.isRecordingVideo, + required this.isTakingPicture, + required this.isStreamingImages, + required this.isRecordingPaused, + required this.flashMode, + required this.exposureMode, + required this.focusMode, + required this.deviceOrientation, + required this.description, + this.lockedCaptureOrientation, + this.recordingOrientation, + this.isPreviewPaused = false, + this.previewPauseOrientation, + }); + + /// Creates a new camera controller state for an uninitialized controller. + const CameraValue.uninitialized(CameraDescription description) + : this( + isInitialized: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + isRecordingPaused: false, + flashMode: FlashMode.auto, + exposureMode: ExposureMode.auto, + focusMode: FocusMode.auto, + deviceOrientation: DeviceOrientation.portraitUp, + isPreviewPaused: false, + description: description, + ); + + /// True after [CameraController.initialize] has completed successfully. + final bool isInitialized; + + /// True when a picture capture request has been sent but as not yet returned. + final bool isTakingPicture; + + /// True when the camera is recording (not the same as previewing). + final bool isRecordingVideo; + + /// True when images from the camera are being streamed. + final bool isStreamingImages; + + /// True when video recording is paused. + final bool isRecordingPaused; + + /// True when the preview widget has been paused manually. + final bool isPreviewPaused; + + /// Set to the orientation the preview was paused in, if it is currently paused. + final DeviceOrientation? previewPauseOrientation; + + /// The size of the preview in pixels. + /// + /// Is `null` until [isInitialized] is `true`. + final Size? previewSize; + + /// The flash mode the camera is currently set to. + final FlashMode flashMode; + + /// The exposure mode the camera is currently set to. + final ExposureMode exposureMode; + + /// The focus mode the camera is currently set to. + final FocusMode focusMode; + + /// The current device UI orientation. + final DeviceOrientation deviceOrientation; + + /// The currently locked capture orientation. + final DeviceOrientation? lockedCaptureOrientation; + + /// Whether the capture orientation is currently locked. + bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; + + /// The orientation of the currently running video recording. + final DeviceOrientation? recordingOrientation; + + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + + /// Creates a modified copy of the object. + /// + /// Explicitly specified fields get the specified value, all other fields get + /// the same value of the current object. + CameraValue copyWith({ + bool? isInitialized, + bool? isRecordingVideo, + bool? isTakingPicture, + bool? isStreamingImages, + Size? previewSize, + bool? isRecordingPaused, + FlashMode? flashMode, + ExposureMode? exposureMode, + FocusMode? focusMode, + bool? exposurePointSupported, + bool? focusPointSupported, + DeviceOrientation? deviceOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, + bool? isPreviewPaused, + CameraDescription? description, + Optional? previewPauseOrientation, + }) { + return CameraValue( + isInitialized: isInitialized ?? this.isInitialized, + previewSize: previewSize ?? this.previewSize, + isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, + isTakingPicture: isTakingPicture ?? this.isTakingPicture, + isStreamingImages: isStreamingImages ?? this.isStreamingImages, + isRecordingPaused: isRecordingPaused ?? this.isRecordingPaused, + flashMode: flashMode ?? this.flashMode, + exposureMode: exposureMode ?? this.exposureMode, + focusMode: focusMode ?? this.focusMode, + deviceOrientation: deviceOrientation ?? this.deviceOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, + isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, + description: description ?? this.description, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, + ); + } + + @override + String toString() { + return '${objectRuntimeType(this, 'CameraValue')}(' + 'isRecordingVideo: $isRecordingVideo, ' + 'isInitialized: $isInitialized, ' + 'previewSize: $previewSize, ' + 'isStreamingImages: $isStreamingImages, ' + 'flashMode: $flashMode, ' + 'exposureMode: $exposureMode, ' + 'focusMode: $focusMode, ' + 'deviceOrientation: $deviceOrientation, ' + 'lockedCaptureOrientation: $lockedCaptureOrientation, ' + 'recordingOrientation: $recordingOrientation, ' + 'isPreviewPaused: $isPreviewPaused, ' + 'previewPausedOrientation: $previewPauseOrientation)'; + } +} + +/// Controls a device camera. +/// +/// This is a stripped-down version of the app-facing controller to serve as a +/// utility for the example and integration tests. It wraps only the calls that +/// have state associated with them, to consolidate tracking of camera state +/// outside of the overall example code. +class CameraController extends ValueNotifier { + /// Creates a new camera controller in an uninitialized state. + CameraController( + CameraDescription cameraDescription, + this.resolutionPreset, { + this.enableAudio = true, + this.imageFormatGroup, + }) : super(CameraValue.uninitialized(cameraDescription)); + + /// The properties of the camera device controlled by this controller. + CameraDescription get description => value.description; + + /// The resolution this controller is targeting. + /// + /// This resolution preset is not guaranteed to be available on the device, + /// if unavailable a lower resolution will be used. + /// + /// See also: [ResolutionPreset]. + final ResolutionPreset resolutionPreset; + + /// Whether to include audio when recording a video. + final bool enableAudio; + + /// The [ImageFormatGroup] describes the output of the raw image format. + /// + /// When null the imageFormat will fallback to the platforms default. + final ImageFormatGroup? imageFormatGroup; + + late int _cameraId; + + bool _isDisposed = false; + StreamSubscription? _imageStreamSubscription; + FutureOr? _initCalled; + StreamSubscription? + _deviceOrientationSubscription; + + /// The camera identifier with which the controller is associated. + int get cameraId => _cameraId; + + /// Initializes the camera on the device. + Future initialize() => _initializeWithDescription(description); + + Future _initializeWithDescription(CameraDescription description) async { + final Completer initializeCompleter = + Completer(); + + _deviceOrientationSubscription = CameraPlatform.instance + .onDeviceOrientationChanged() + .listen((DeviceOrientationChangedEvent event) { + value = value.copyWith( + deviceOrientation: event.orientation, + ); + }); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); + + unawaited(CameraPlatform.instance + .onCameraInitialized(_cameraId) + .first + .then((CameraInitializedEvent event) { + initializeCompleter.complete(event); + })); + + await CameraPlatform.instance.initializeCamera( + _cameraId, + imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, + ); + + value = value.copyWith( + isInitialized: true, + description: description, + previewSize: await initializeCompleter.future + .then((CameraInitializedEvent event) => Size( + event.previewWidth, + event.previewHeight, + )), + exposureMode: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.exposureMode), + focusMode: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.focusMode), + exposurePointSupported: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.exposurePointSupported), + focusPointSupported: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.focusPointSupported), + ); + + _initCalled = true; + } + + /// Prepare the capture session for video recording. + Future prepareForVideoRecording() async { + await CameraPlatform.instance.prepareForVideoRecording(); + } + + /// Pauses the current camera preview + Future pausePreview() async { + await CameraPlatform.instance.pausePreview(_cameraId); + value = value.copyWith( + isPreviewPaused: true, + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); + } + + /// Resumes the current camera preview + Future resumePreview() async { + await CameraPlatform.instance.resumePreview(_cameraId); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); + } + + /// Sets the description of the camera. + Future setDescription(CameraDescription description) async { + if (value.isRecordingVideo) { + await CameraPlatform.instance.setDescriptionWhileRecording(description); + value = value.copyWith(description: description); + } else { + await _initializeWithDescription(description); + } + } + + /// Captures an image and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture fails. + Future takePicture() async { + value = value.copyWith(isTakingPicture: true); + final XFile file = await CameraPlatform.instance.takePicture(_cameraId); + value = value.copyWith(isTakingPicture: false); + return file; + } + + /// Start streaming images from platform camera. + Future startImageStream( + Function(CameraImageData image) onAvailable) async { + _imageStreamSubscription = CameraPlatform.instance + .onStreamedFrameAvailable(_cameraId) + .listen((CameraImageData imageData) { + onAvailable(imageData); + }); + value = value.copyWith(isStreamingImages: true); + } + + /// Stop streaming images from platform camera. + Future stopImageStream() async { + value = value.copyWith(isStreamingImages: false); + await _imageStreamSubscription?.cancel(); + _imageStreamSubscription = null; + } + + /// Start a video recording. + /// + /// The video is returned as a [XFile] after calling [stopVideoRecording]. + /// Throws a [CameraException] if the capture fails. + Future startVideoRecording( + {Function(CameraImageData image)? streamCallback}) async { + await CameraPlatform.instance.startVideoCapturing( + VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); + value = value.copyWith( + isRecordingVideo: true, + isRecordingPaused: false, + isStreamingImages: streamCallback != null, + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); + } + + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { + if (value.isStreamingImages) { + await stopImageStream(); + } + + final XFile file = + await CameraPlatform.instance.stopVideoRecording(_cameraId); + value = value.copyWith( + isRecordingVideo: false, + isRecordingPaused: false, + recordingOrientation: const Optional.absent(), + ); + return file; + } + + /// Pause video recording. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future pauseVideoRecording() async { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: true); + } + + /// Resume video recording after pausing. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future resumeVideoRecording() async { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: false); + } + + /// Returns a widget showing a live camera preview. + Widget buildPreview() { + return CameraPlatform.instance.buildPreview(_cameraId); + } + + /// Sets the flash mode for taking pictures. + Future setFlashMode(FlashMode mode) async { + await CameraPlatform.instance.setFlashMode(_cameraId, mode); + value = value.copyWith(flashMode: mode); + } + + /// Sets the exposure mode for taking pictures. + Future setExposureMode(ExposureMode mode) async { + await CameraPlatform.instance.setExposureMode(_cameraId, mode); + value = value.copyWith(exposureMode: mode); + } + + /// Sets the exposure offset for the selected camera. + Future setExposureOffset(double offset) async { + // Check if offset is in range + final List range = await Future.wait(>[ + CameraPlatform.instance.getMinExposureOffset(_cameraId), + CameraPlatform.instance.getMaxExposureOffset(_cameraId) + ]); + + // Round to the closest step if needed + final double stepSize = + await CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); + if (stepSize > 0) { + final double inv = 1.0 / stepSize; + double roundedOffset = (offset * inv).roundToDouble() / inv; + if (roundedOffset > range[1]) { + roundedOffset = (offset * inv).floorToDouble() / inv; + } else if (roundedOffset < range[0]) { + roundedOffset = (offset * inv).ceilToDouble() / inv; + } + offset = roundedOffset; + } + + return CameraPlatform.instance.setExposureOffset(_cameraId, offset); + } + + /// Locks the capture orientation. + /// + /// If [orientation] is omitted, the current device orientation is used. + Future lockCaptureOrientation() async { + await CameraPlatform.instance + .lockCaptureOrientation(_cameraId, value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: + Optional.of(value.deviceOrientation)); + } + + /// Unlocks the capture orientation. + Future unlockCaptureOrientation() async { + await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); + } + + /// Sets the focus mode for taking pictures. + Future setFocusMode(FocusMode mode) async { + await CameraPlatform.instance.setFocusMode(_cameraId, mode); + value = value.copyWith(focusMode: mode); + } + + /// Releases the resources of this camera. + @override + Future dispose() async { + if (_isDisposed) { + return; + } + _isDisposed = true; + await _deviceOrientationSubscription?.cancel(); + super.dispose(); + if (_initCalled != null) { + await _initCalled; + await CameraPlatform.instance.dispose(_cameraId); + } + } + + @override + void removeListener(VoidCallback listener) { + // Prevent ValueListenableBuilder in CameraPreview widget from causing an + // exception to be thrown by attempting to remove its own listener after + // the controller has already been disposed. + if (!_isDisposed) { + super.removeListener(listener); + } + } +} + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/ohos/automatic_camera_test/lib/camera_preview.dart b/ohos/automatic_camera_test/lib/camera_preview.dart new file mode 100644 index 00000000..629409cf --- /dev/null +++ b/ohos/automatic_camera_test/lib/camera_preview.dart @@ -0,0 +1,87 @@ + // Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'dart:math' as math; + +import 'camera_controller.dart'; + +/// A widget showing a live camera preview. +class CameraPreview extends StatelessWidget { + /// Creates a preview widget for the given camera controller. + const CameraPreview(this.controller, {super.key, this.child}); + + /// The controller for the camera that the preview is shown for. + final CameraController controller; + + /// A widget to overlay on top of the camera preview + final Widget? child; + + @override + Widget build(BuildContext context) { + + return controller.value.isInitialized + ? ValueListenableBuilder( + valueListenable: controller, + builder: (BuildContext context, Object? value, Widget? child) { + final double cameraAspectRatio = + controller.value.previewSize!.width / + controller.value.previewSize!.height; + return AspectRatio( + aspectRatio: _isLandscape() + ? cameraAspectRatio + : (1 / cameraAspectRatio), + child: Stack( + fit: StackFit.expand, + children: [ + _wrapInRotatedBox(child: controller.buildPreview()), + child ?? Container(), + ], + ), + ); + }, + child: child, + ) + : Container(); + } + + Widget _wrapInRotatedBox({required Widget child}) { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.ohos) { + return child; + } + + return RotatedBox( + quarterTurns: _getQuarterTurns(), + child: child, + ); + } + + bool _isLandscape() { + return [ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight + ].contains(_getApplicableOrientation()); + } + + int _getQuarterTurns() { + final Map turns = { + DeviceOrientation.portraitUp: 0, + DeviceOrientation.landscapeRight: 1, + DeviceOrientation.portraitDown: 2, + DeviceOrientation.landscapeLeft: 3, + }; + return turns[_getApplicableOrientation()]!; + } + + DeviceOrientation _getApplicableOrientation() { + return controller.value.isRecordingVideo + ? controller.value.recordingOrientation! + : (controller.value.previewPauseOrientation ?? + controller.value.lockedCaptureOrientation ?? + controller.value.deviceOrientation); + } +} diff --git a/ohos/automatic_camera_test/lib/fileselector/file_selector.dart b/ohos/automatic_camera_test/lib/fileselector/file_selector.dart new file mode 100644 index 00000000..693746d5 --- /dev/null +++ b/ohos/automatic_camera_test/lib/fileselector/file_selector.dart @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'file_selector_api.dart'; + +class FileSelector { + FileSelector({ FileSelectorApi? api}) + : _api = api ?? FileSelectorApi(); + + final FileSelectorApi _api; + + /// Registers this class as the implementation of the file_selector platform interface. + /// + @override + Future openFileByPath(String path) async { + final int? fd = await _api.openFileByPath(path); + print("openfile#"); + print(fd); + return fd; + } +} \ No newline at end of file diff --git a/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart b/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart new file mode 100644 index 00000000..8ff6df7c --- /dev/null +++ b/ohos/automatic_camera_test/lib/fileselector/file_selector_api.dart @@ -0,0 +1,142 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'dart:async'; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class FileResponse { + FileResponse({ + required this.path, + this.name, + required this.fd, + }); + + String path; + + String? name; + + int fd; + + Object encode() { + return [ + path, + name, + fd, + ]; + } + + static FileResponse decode(Object result) { + result as List; + return FileResponse( + path: result[0]! as String, + name: result[1] as String?, + fd: result[2]! as int, + ); + } +} + +class FileTypes { + FileTypes({ + required this.mimeTypes, + required this.extensions, + }); + + List mimeTypes; + + List extensions; + + Object encode() { + return [ + mimeTypes, + extensions, + ]; + } + + static FileTypes decode(Object result) { + result as List; + return FileTypes( + mimeTypes: (result[0] as List?)!.cast(), + extensions: (result[1] as List?)!.cast(), + ); + } +} + +class _FileSelectorApiCodec extends StandardMessageCodec { + const _FileSelectorApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is FileResponse) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is FileTypes) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return FileResponse.decode(readValue(buffer)!); + case 129: + return FileTypes.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// An API to call to native code to select files or directories. +class FileSelectorApi { + /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + FileSelectorApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _FileSelectorApiCodec(); + + /// Opens a file dialog for loading files and returns a file path. + /// + /// Returns `null` if user cancels the operation. + Future openFileByPath(String path) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.FileSelectorApi.openFileByPath', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([path]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as int?); + } + } + +} diff --git a/ohos/automatic_camera_test/lib/main.dart b/ohos/automatic_camera_test/lib/main.dart new file mode 100644 index 00000000..8d4b760a --- /dev/null +++ b/ohos/automatic_camera_test/lib/main.dart @@ -0,0 +1,1087 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:camera_ohos/camera_ohos.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:video_player/video_player.dart'; + +import 'camera_controller.dart'; +import 'camera_preview.dart'; +import 'fileselector/file_selector.dart'; + +/// Camera example home widget. +class CameraExampleHome extends StatefulWidget { + /// Default Constructor + const CameraExampleHome({super.key}); + + @override + State createState() { + return _CameraExampleHomeState(); + } +} + +/// Returns a suitable camera icon for [direction]. +IconData getCameraLensIcon(CameraLensDirection direction) { + switch (direction) { + case CameraLensDirection.back: + return Icons.camera_rear; + case CameraLensDirection.front: + return Icons.camera_front; + case CameraLensDirection.external: + return Icons.camera; + } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; +} + +void _logError(String code, String? message) { + // ignore: avoid_print + print('Error: $code${message == null ? '' : '\nError Message: $message'}'); +} + +class _CameraExampleHomeState extends State + with WidgetsBindingObserver, TickerProviderStateMixin { + CameraController? controller; + XFile? imageFile; + XFile? videoFile; + VideoPlayerController? videoController; + VoidCallback? videoPlayerListener; + bool enableAudio = true; + double _minAvailableExposureOffset = 0.0; + double _maxAvailableExposureOffset = 0.0; + double _currentExposureOffset = 0.0; + late AnimationController _flashModeControlRowAnimationController; + late Animation _flashModeControlRowAnimation; + late AnimationController _exposureModeControlRowAnimationController; + late Animation _exposureModeControlRowAnimation; + late AnimationController _focusModeControlRowAnimationController; + late Animation _focusModeControlRowAnimation; + double _minAvailableZoom = 1.0; + double _maxAvailableZoom = 1.0; + double _currentScale = 1.0; + double _baseScale = 1.0; + + // Counting pointers (number of user fingers on screen) + int _pointers = 0; + + @override + void initState() { + super.initState(); + + _ambiguate(WidgetsBinding.instance)?.addObserver(this); + + _flashModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _flashModeControlRowAnimation = CurvedAnimation( + parent: _flashModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _exposureModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _exposureModeControlRowAnimation = CurvedAnimation( + parent: _exposureModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _focusModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _focusModeControlRowAnimation = CurvedAnimation( + parent: _focusModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + } + + @override + void dispose() { + _ambiguate(WidgetsBinding.instance)?.removeObserver(this); + _flashModeControlRowAnimationController.dispose(); + _exposureModeControlRowAnimationController.dispose(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final CameraController? cameraController = controller; + + // App state changed before we got the chance to initialize. + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + + if (state == AppLifecycleState.inactive) { + cameraController.dispose(); + } else if (state == AppLifecycleState.resumed) { + _initializeCameraController(cameraController.description); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Camera example'), + ), + body: Column( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.black, + border: Border.all( + color: + controller != null && controller!.value.isRecordingVideo + ? Colors.redAccent + : Colors.grey, + width: 3.0, + ), + ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Center( + child: _cameraPreviewWidget(), + ), + ), + ), + ), + _captureControlRowWidget(), + _modeControlRowWidget(), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + _cameraTogglesRowWidget(), + _thumbnailWidget(), + ], + ), + ), + ], + ), + ); + } + + /// Display the preview from the camera (or a message if the preview is not available). + Widget _cameraPreviewWidget() { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + return const Text( + 'Tap a camera', + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.w900, + ), + ); + } else { + return Listener( + onPointerDown: (_) => _pointers++, + onPointerUp: (_) => _pointers--, + child: CameraPreview( + controller!, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onTapDown: (TapDownDetails details) => + onViewFinderTap(details, constraints), + ); + }), + ), + ); + } + } + + void _handleScaleStart(ScaleStartDetails details) { + _baseScale = _currentScale; + } + + Future _handleScaleUpdate(ScaleUpdateDetails details) async { + // When there are not exactly two fingers on screen don't scale + if (controller == null || _pointers != 2) { + return; + } + + _currentScale = (_baseScale * details.scale) + .clamp(_minAvailableZoom, _maxAvailableZoom); + + await CameraPlatform.instance + .setZoomLevel(controller!.cameraId, _currentScale); + } + + /// Display the thumbnail of the captured image or video. + Widget _thumbnailWidget() { + final VideoPlayerController? localVideoController = videoController; + + return Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (localVideoController == null && imageFile == null) + Container() + else + SizedBox( + width: 64.0, + height: 64.0, + child: (localVideoController == null) + ? ( + // The captured image on the web contains a network-accessible URL + // pointing to a location within the browser. It may be displayed + // either with Image.network or Image.memory after loading the image + // bytes to memory. + kIsWeb + ? Image.network(imageFile!.path) + : Image.file(File(imageFile!.path))) + : Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.pink)), + child: Center( + child: AspectRatio( + aspectRatio: + localVideoController.value.aspectRatio, + child: VideoPlayer(localVideoController)), + ), + ), + ), + ], + ), + ), + ); + } + + /// Display a bar with buttons to change the flash and exposure modes + Widget _modeControlRowWidget() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.flash_on), + color: Colors.blue, + onPressed: controller != null ? onFlashModeButtonPressed : null, + ), + // The exposure and focus mode are currently not supported on the web. + ...!kIsWeb + ? [ + IconButton( + icon: const Icon(Icons.exposure), + color: Colors.blue, + onPressed: controller != null + ? onExposureModeButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: + controller != null ? onFocusModeButtonPressed : null, + ) + ] + : [], + IconButton( + icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), + color: Colors.blue, + onPressed: controller != null ? onAudioModeButtonPressed : null, + ), + IconButton( + icon: Icon(controller?.value.isCaptureOrientationLocked ?? false + ? Icons.screen_lock_rotation + : Icons.screen_rotation), + color: Colors.blue, + onPressed: controller != null + ? onCaptureOrientationLockButtonPressed + : null, + ), + ], + ), + _flashModeControlRowWidget(), + _exposureModeControlRowWidget(), + _focusModeControlRowWidget(), + ], + ); + } + + Widget _flashModeControlRowWidget() { + return SizeTransition( + sizeFactor: _flashModeControlRowAnimation, + child: ClipRect( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.flash_off), + color: controller?.value.flashMode == FlashMode.off + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.off) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_auto), + color: controller?.value.flashMode == FlashMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.auto) + : null, + ), + IconButton( + icon: const Icon(Icons.flash_on), + color: controller?.value.flashMode == FlashMode.always + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.always) + : null, + ), + IconButton( + icon: const Icon(Icons.highlight), + color: controller?.value.flashMode == FlashMode.torch + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.torch) + : null, + ), + ], + ), + ), + ); + } + + Widget _exposureModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + foregroundColor: controller?.value.exposureMode == ExposureMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + foregroundColor: controller?.value.exposureMode == ExposureMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _exposureModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + const Center( + child: Text('Exposure Mode'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + style: styleAuto, + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.auto) + : null, + onLongPress: () { + if (controller != null) { + CameraPlatform.instance + .setExposurePoint(controller!.cameraId, null); + showInSnackBar('Resetting exposure point'); + } + }, + child: const Text('AUTO'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.locked) + : null, + child: const Text('LOCKED'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => controller!.setExposureOffset(0.0) + : null, + child: const Text('RESET OFFSET'), + ), + ], + ), + const Center( + child: Text('Exposure Offset'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text(_minAvailableExposureOffset.toString()), + Slider( + value: _currentExposureOffset, + min: _minAvailableExposureOffset, + max: _maxAvailableExposureOffset, + label: _currentExposureOffset.toString(), + onChanged: _minAvailableExposureOffset == + _maxAvailableExposureOffset + ? null + : setExposureOffset, + ), + Text(_maxAvailableExposureOffset.toString()), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _focusModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + foregroundColor: controller?.value.focusMode == FocusMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + foregroundColor: controller?.value.focusMode == FocusMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _focusModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + const Center( + child: Text('Focus Mode'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + style: styleAuto, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.auto) + : null, + onLongPress: () { + if (controller != null) { + CameraPlatform.instance + .setFocusPoint(controller!.cameraId, null); + } + showInSnackBar('Resetting focus point'); + }, + child: const Text('AUTO'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.locked) + : null, + child: const Text('LOCKED'), + ), + ], + ), + ], + ), + ), + ), + ); + } + + /// Display the control bar with buttons to take pictures and record videos. + Widget _captureControlRowWidget() { + final CameraController? cameraController = controller; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.camera_alt), + color: Colors.blue, + onPressed: cameraController != null && + cameraController.value.isInitialized && + !cameraController.value.isRecordingVideo + ? onTakePictureButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.videocam), + color: Colors.blue, + onPressed: cameraController != null && + cameraController.value.isInitialized && + !cameraController.value.isRecordingVideo + ? onVideoRecordButtonPressed + : null, + ), + IconButton( + icon: cameraController != null && + (!cameraController.value.isRecordingVideo || + cameraController.value.isRecordingPaused) + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), + color: Colors.blue, + onPressed: cameraController != null && + cameraController.value.isInitialized && + cameraController.value.isRecordingVideo + ? (cameraController.value.isRecordingPaused) + ? onResumeButtonPressed + : onPauseButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.stop), + color: Colors.red, + onPressed: cameraController != null && + cameraController.value.isInitialized && + cameraController.value.isRecordingVideo + ? onStopButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.pause_presentation), + color: + cameraController != null && cameraController.value.isPreviewPaused + ? Colors.red + : Colors.blue, + onPressed: + cameraController == null ? null : onPausePreviewButtonPressed, + ), + ], + ); + } + + /// Display a row of toggle to select the camera (or a message if no camera is available). + Widget _cameraTogglesRowWidget() { + final List toggles = []; + + void onChanged(CameraDescription? description) { + if (description == null) { + return; + } + + onNewCameraSelected(description); + } + + if (_cameras.isEmpty) { + _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async { + showInSnackBar('No camera found.'); + }); + return const Text('None'); + } else { + for (final CameraDescription cameraDescription in _cameras) { + toggles.add( + SizedBox( + width: 90.0, + child: RadioListTile( + title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), + groupValue: controller?.description, + value: cameraDescription, + onChanged: onChanged, + ), + ), + ); + } + } + + return Row(children: toggles); + } + + String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); + + void showInSnackBar(String message) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(message))); + } + + void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { + if (controller == null) { + return; + } + + final CameraController cameraController = controller!; + + final Point point = Point( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + ); + CameraPlatform.instance.setExposurePoint(cameraController.cameraId, point); + CameraPlatform.instance.setFocusPoint(cameraController.cameraId, point); + } + + Future onNewCameraSelected(CameraDescription cameraDescription) async { + if (controller != null) { + return controller!.setDescription(cameraDescription); + } else { + return _initializeCameraController(cameraDescription); + } + } + + Future _initializeCameraController( + CameraDescription cameraDescription) async { + final CameraController cameraController = CameraController( + cameraDescription, + kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, + enableAudio: enableAudio, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + controller = cameraController; + + // If the controller is updated then update the UI. + cameraController.addListener(() { + if (mounted) { + setState(() {}); + } + }); + + try { + await cameraController.initialize(); + await Future.wait(>[ + // The exposure mode is currently not supported on the web. + ...!kIsWeb + ? >[ + CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId) + .then( + (double value) => _minAvailableExposureOffset = value), + CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId) + .then((double value) => _maxAvailableExposureOffset = value) + ] + : >[], + CameraPlatform.instance + .getMaxZoomLevel(cameraController.cameraId) + .then((double value) => _maxAvailableZoom = value), + CameraPlatform.instance + .getMinZoomLevel(cameraController.cameraId) + .then((double value) => _minAvailableZoom = value), + ]); + } on CameraException catch (e) { + switch (e.code) { + case 'CameraAccessDenied': + showInSnackBar('You have denied camera access.'); + break; + case 'CameraAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable camera access.'); + break; + case 'CameraAccessRestricted': + // iOS only + showInSnackBar('Camera access is restricted.'); + break; + case 'AudioAccessDenied': + showInSnackBar('You have denied audio access.'); + break; + case 'AudioAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable audio access.'); + break; + case 'AudioAccessRestricted': + // iOS only + showInSnackBar('Audio access is restricted.'); + break; + case 'cameraPermission': + // Android & web only + showInSnackBar('Unknown permission error.'); + break; + default: + _showCameraException(e); + break; + } + } + + if (mounted) { + setState(() {}); + } + } + + void onTakePictureButtonPressed() { + takePicture().then((XFile? file) { + if (mounted) { + setState(() { + imageFile = file; + videoController?.dispose(); + videoController = null; + }); + if (file != null) { + showInSnackBar('Picture saved to ${file.path}'); + } + } + }); + } + + void onFlashModeButtonPressed() { + if (_flashModeControlRowAnimationController.value == 1) { + _flashModeControlRowAnimationController.reverse(); + } else { + _flashModeControlRowAnimationController.forward(); + _exposureModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onExposureModeButtonPressed() { + if (_exposureModeControlRowAnimationController.value == 1) { + _exposureModeControlRowAnimationController.reverse(); + } else { + _exposureModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onFocusModeButtonPressed() { + if (_focusModeControlRowAnimationController.value == 1) { + _focusModeControlRowAnimationController.reverse(); + } else { + _focusModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _exposureModeControlRowAnimationController.reverse(); + } + } + + void onAudioModeButtonPressed() { + enableAudio = !enableAudio; + if (controller != null) { + onNewCameraSelected(controller!.description); + } + } + + Future onCaptureOrientationLockButtonPressed() async { + try { + if (controller != null) { + final CameraController cameraController = controller!; + if (cameraController.value.isCaptureOrientationLocked) { + await cameraController.unlockCaptureOrientation(); + showInSnackBar('Capture orientation unlocked'); + } else { + await cameraController.lockCaptureOrientation(); + showInSnackBar( + 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + } + } + } on CameraException catch (e) { + _showCameraException(e); + } + } + + void onSetFlashModeButtonPressed(FlashMode mode) { + setFlashMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); + }); + } + + void onSetExposureModeButtonPressed(ExposureMode mode) { + setExposureMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); + }); + } + + void onSetFocusModeButtonPressed(FocusMode mode) { + setFocusMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); + }); + } + + void onVideoRecordButtonPressed() { + startVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + }); + } + + void onStopButtonPressed() { + stopVideoRecording().then((XFile? file) { + if (mounted) { + setState(() {}); + } + if (file != null) { + showInSnackBar('Video recorded to ${file.path}'); + videoFile = file; + _startVideoPlayer(); + } + }); + } + + Future onPausePreviewButtonPressed() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isPreviewPaused) { + await cameraController.resumePreview(); + } else { + await cameraController.pausePreview(); + } + + if (mounted) { + setState(() {}); + } + } + + void onPauseButtonPressed() { + pauseVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Video recording paused'); + }); + } + + void onResumeButtonPressed() { + resumeVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Video recording resumed'); + }); + } + + Future startVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isRecordingVideo) { + // A recording is already started, do nothing. + return; + } + + try { + await cameraController.startVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + return; + } + } + + Future stopVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return null; + } + + try { + return cameraController.stopVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + Future pauseVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return; + } + + try { + await cameraController.pauseVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future resumeVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return; + } + + try { + await cameraController.resumeVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setFlashMode(FlashMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setFlashMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureMode(ExposureMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setExposureMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureOffset(double offset) async { + if (controller == null) { + return; + } + + setState(() { + _currentExposureOffset = offset; + }); + try { + offset = await controller!.setExposureOffset(offset); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setFocusMode(FocusMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setFocusMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future _startVideoPlayer() async { + if (videoFile == null) { + return; + } + final VideoPlayerController vController; + if (Platform.operatingSystem == 'ohos') { + final FileSelector instance = FileSelector(); + int? fileFd = await instance.openFileByPath(videoFile!.path); + vController = VideoPlayerController.fileFd(fileFd!); + } else { + vController = kIsWeb? VideoPlayerController.network(videoFile!.path) + : VideoPlayerController.file(File(videoFile!.path)); + } + videoPlayerListener = () { + if (videoController != null) { + // Refreshing the state to update video player with the correct ratio. + if (mounted) { + setState(() {}); + } + videoController!.removeListener(videoPlayerListener!); + } + }; + vController.addListener(videoPlayerListener!); + await vController.setLooping(true); + await vController.initialize(); + await videoController?.dispose(); + if (mounted) { + setState(() { + imageFile = null; + videoController = vController; + }); + } + await vController.play(); + print("========_startVideoPlayer end: "+videoFile!.path); + } + + Future takePicture() async { + final CameraController? cameraController = controller; + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return null; + } + + if (cameraController.value.isTakingPicture) { + // A capture is already pending, do nothing. + return null; + } + + try { + final XFile file = await cameraController.takePicture(); + return file; + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + void _showCameraException(CameraException e) { + _logError(e.code, e.description); + showInSnackBar('Error: ${e.code}\n${e.description}'); + } +} + +/// CameraApp is the Main Application. +class CameraApp extends StatelessWidget { + /// Default Constructor + const CameraApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: CameraExampleHome(), + ); + } +} + +List _cameras = []; + +Future main() async { + // Fetch the available cameras before initializing the app. + try { + WidgetsFlutterBinding.ensureInitialized(); + _cameras = await CameraPlatform.instance.availableCameras(); + } on CameraException catch (e) { + _logError(e.code, e.description); + } + runApp(const CameraApp()); +} + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/ohos/automatic_camera_test/pubspec.yaml b/ohos/automatic_camera_test/pubspec.yaml new file mode 100644 index 00000000..05dd2b53 --- /dev/null +++ b/ohos/automatic_camera_test/pubspec.yaml @@ -0,0 +1,60 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: automatic_camera_test +description: Demonstrates how to use the camera plugin. +publish_to: none + +environment: + sdk: ">=2.19.0 <4.0.0" + flutter: ">=3.7.0" + +dependencies: + camera_ohos: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/camera/camera_ohos" + camera_platform_interface: ^2.5.0 + flutter: + sdk: flutter + quiver: ^3.0.0 + path_provider: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/path_provider/path_provider" + video_player: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/video_player/video_player" + +dependency_overrides: + path_provider_ohos: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/path_provider/path_provider_ohos" + video_player_ohos: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/video_player/video_player_ohos" + +dev_dependencies: + build_runner: ^2.1.10 + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/ohos/automatic_camera_test/test/widget_test.dart b/ohos/automatic_camera_test/test/widget_test.dart new file mode 100644 index 00000000..9f6d5823 --- /dev/null +++ b/ohos/automatic_camera_test/test/widget_test.dart @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:automatic_camera_test/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/ohos/automatic_camera_test/test_driver/integration_test.dart b/ohos/automatic_camera_test/test_driver/integration_test.dart new file mode 100644 index 00000000..aa57599f --- /dev/null +++ b/ohos/automatic_camera_test/test_driver/integration_test.dart @@ -0,0 +1,66 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: avoid_print + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +const String _examplePackage = 'io.flutter.plugins.cameraexample'; + +Future main() async { + if (!(Platform.isLinux || Platform.isMacOS)) { + print('This test must be run on a POSIX host. Skipping...'); + exit(0); + } + final bool adbExists = + Process.runSync('which', ['adb']).exitCode == 0; + if (!adbExists) { + print(r'This test needs ADB to exist on the $PATH. Skipping...'); + exit(0); + } + print('Granting camera permissions...'); + Process.runSync('adb', [ + 'shell', + 'pm', + 'grant', + _examplePackage, + 'android.permission.CAMERA' + ]); + Process.runSync('adb', [ + 'shell', + 'pm', + 'grant', + _examplePackage, + 'android.permission.RECORD_AUDIO' + ]); + print('Starting test.'); + final FlutterDriver driver = await FlutterDriver.connect(); + final String data = await driver.requestData( + null, + timeout: const Duration(minutes: 1), + ); + await driver.close(); + print('Test finished. Revoking camera permissions...'); + Process.runSync('adb', [ + 'shell', + 'pm', + 'revoke', + _examplePackage, + 'android.permission.CAMERA' + ]); + Process.runSync('adb', [ + 'shell', + 'pm', + 'revoke', + _examplePackage, + 'android.permission.RECORD_AUDIO' + ]); + + final Map result = jsonDecode(data) as Map; + exit(result['result'] == 'true' ? 0 : 1); +} -- Gitee From fad336d3bae5ba5b00d1e9036d7528cd8f513f9a Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:25:03 +0800 Subject: [PATCH 10/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4video=5Fplayer?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../ohos/AppScope/app.json5 | 10 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../ohos/build-profile.json5 | 41 ++++ .../ohos/entry/build-profile.json5 | 29 +++ .../ohos/entry/hvigorfile.ts | 17 ++ .../main/ets/entryability/EntryAbility.ets | 27 +++ .../main/ets/fileselector/FileSelector.ets | 104 ++++++++++ .../fileselector/FileSelectorOhosPlugin.ets | 56 +++++ .../fileselector/GeneratedFileSelectorApi.ets | 194 ++++++++++++++++++ .../ohos/entry/src/main/ets/pages/Index.ets | 38 ++++ .../ets/plugins/GeneratedPluginRegistrant.ets | 30 +++ .../main/resources/base/element/color.json | 8 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../src/ohosTest/ets/test/Ability.test.ets | 50 +++++ .../resources/base/element/color.json | 8 + .../ohosTest/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../ohos/hvigor/hvigor-config.json5 | 9 + .../ohos/hvigorfile.ts | 21 ++ 18 files changed, 642 insertions(+) create mode 100644 ohos/automatic_video_player_test/ohos/AppScope/app.json5 create mode 100644 ohos/automatic_video_player_test/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 ohos/automatic_video_player_test/ohos/build-profile.json5 create mode 100644 ohos/automatic_video_player_test/ohos/entry/build-profile.json5 create mode 100644 ohos/automatic_video_player_test/ohos/entry/hvigorfile.ts create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/FileSelector.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/ets/pages/Index.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/color.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/media/icon.png create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/media/icon.png create mode 100644 ohos/automatic_video_player_test/ohos/hvigor/hvigor-config.json5 create mode 100644 ohos/automatic_video_player_test/ohos/hvigorfile.ts diff --git a/ohos/automatic_video_player_test/ohos/AppScope/app.json5 b/ohos/automatic_video_player_test/ohos/AppScope/app.json5 new file mode 100644 index 00000000..0d414d89 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.example", + "vendor": "example", + "versionCode": 1, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/AppScope/resources/base/media/app_icon.png b/ohos/automatic_video_player_test/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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: void { + try { + let documentSelectOptions = new picker.DocumentSelectOptions(); + documentSelectOptions.maxSelectNumber = 1; + let file_suffix: Array = []; + if (allowedTypes.extensions != null) { + for (let extensionType of allowedTypes.extensions) { + file_suffix.push("." + extensionType); + } + } + documentSelectOptions.fileSuffixFilters = file_suffix; + let uris: Array = []; + const documentViewPicker = new picker.DocumentViewPicker(); + documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array) => { + uris = documentSelectResult; + Log.i(TAG, 'documentViewPicker select file succeed and uris are:' + uris); + let file = fs.openSync(uris[0], fs.OpenMode.READ_ONLY); + let response = new FileResponse(file.path, file.name, file.fd); + result.success(response); + }).catch((err: BusinessError) => { + Log.e(TAG, 'Invoke documentPickerSelect failed with err: ' + err); + result.error(new Error("Failed to read file: " + err.message)); + }) + } catch (err) { + Log.e(TAG, 'documentPickerSelect select failed with err: ' + err); + result.error(new Error("Failed to read file")); + } + } + + static getCodec(): MessageCodec { + return FileSelectorApiCodec.INSTANCE; + } + + setup(binaryMessenger: BinaryMessenger, abilityPluginBinding: AbilityPluginBinding): void { + let api = this; + { + this.binding = abilityPluginBinding; + const channel: BasicMessageChannel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.FileSelectorApi.openFile", FileSelector.getCodec()); + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + Log.d(TAG, 'onMessage reply:' + reply) + const wrapped: Array = new Array(); + const args: Array = msg as Array; + const allowedTypesArg = args[0] as FileTypes; + const resultCallback: Result = new ResultBuilder((result: FileResponse): void => { + wrapped.push(result); + reply.reply(wrapped); + },(error: Error): void => { + const wrappedError: ArrayList = msg.wrapError(error); + reply.reply(wrappedError); + }) + api.openFileWithExtensions(allowedTypesArg, resultCallback); + } + }); + } + } +} + +class ResultBuilder{ + success : (result: FileResponse)=>void + error: (error: Error) =>void + + constructor(success:ESObject , error:ESObject) { + this.success = success + this.error = error + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets new file mode 100644 index 00000000..2b3e4179 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/FileSelectorOhosPlugin.ets @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import AbilityAware from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware'; +import { + AbilityPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding'; +import { + FlutterPlugin, + FlutterPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; +import { FileSelector } from './FileSelector' + +const TAG = "FileSelectorOhosPlugin" + +export default class FileSelectorOhosPlugin implements FlutterPlugin, AbilityAware { + + private pluginBinding: FlutterPluginBinding | null = null; + private fileSelectorApi: FileSelector | null = null; + + getUniqueClassName(): string { + return "FileSelectorOhosPlugin" + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + this.fileSelectorApi = new FileSelector(binding); + if (this.pluginBinding != null) { + this.fileSelectorApi.setup(this.pluginBinding.getBinaryMessenger(), binding); + } + } + + onDetachedFromAbility(): void { + this.fileSelectorApi = null; + } + + onAttachedToEngine(binding: FlutterPluginBinding): void { + console.debug(TAG, 'onAttachedToEngine file selector ') + this.pluginBinding = binding; + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + this.pluginBinding = null; + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets new file mode 100644 index 00000000..2d1f8cbe --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/fileselector/GeneratedFileSelectorApi.ets @@ -0,0 +1,194 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { ByteBuffer } from '@ohos/flutter_ohos/src/main/ets/util/ByteBuffer'; +import StandardMessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec'; +import Log from '@ohos/flutter_ohos/src/main/ets/util/Log'; + +const TAG = "GeneratedFileSelectorApi"; + +class FlutterError extends Error { + /** The error code. */ + public code: string; + + /** The error details. Must be a datatype supported by the api codec. */ + public details: ESObject; + + constructor(code: string, message: string, details: ESObject) { + super(message); + this.code = code; + this.details = details; + } +} + +export function wrapError(exception: Error): Array { + let errorList: Array = new Array(); + if (exception instanceof FlutterError) { + let error = exception as FlutterError; + errorList.push(error.code); + errorList.push(error.message); + errorList.push(error.details); + } else { + errorList.push(exception.toString()); + errorList.push(exception.name); + errorList.push( + "Cause: " + exception.message + ", Stacktrace: " + exception.stack); + } + return errorList; +} + +export class FileResponse { + private path: string; + + public getPath(): string { + return this.path; + } + + public setPath(setterArg: string): void { + if (setterArg == null) { + throw new Error('Nonnull field \'path\' is null.'); + } + this.path = setterArg; + } + + private name: string; + + public getName(): string { + return this.name; + } + + public setName(setterArg: string): void { + this.name = setterArg; + } + + private fd: number; + + public getFd(): number { + return this.fd; + } + + public setFd(setterArg: number): void { + if (setterArg == null) { + throw new Error("Nonnull field \"fd\" is null."); + } + this.fd = setterArg; + } + + constructor(path: string, name: string, fd: number) { + this.path = path; + this.name = name; + this.fd = fd; + } + + toList(): Array { + let toListResult: Array = new Array(); + toListResult.push(this.path); + toListResult.push(this.name); + toListResult.push(this.fd); + return toListResult; + } + + static fromList(list: Array): FileResponse { + let path: ESObject = list[0]; + let name: ESObject = list[1]; + let fd: ESObject = list[2]; + let response = new FileResponse(path, name, fd); + return response; + } +} + +export class FileTypes { + mimeTypes: Array = []; + + getMimeTypes(): Array { + return this.mimeTypes; + } + + setMimeTypes(setterArg: Array | null): void { + if (setterArg == null) { + throw new Error("Nonnull field \"mimeTypes\" is null."); + } + this.mimeTypes = setterArg; + } + + extensions: Array = []; + + getExtensions(): Array { + return this.extensions; + } + + setExtensions(setterArg: Array): void { + if (setterArg == null) { + throw new Error("Nonnull field \"extensions\" is null."); + } + this.extensions = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + FileTypes() {} + + toList(): Array { + let toListResult: Array = new Array(); + toListResult.push(this.mimeTypes); + toListResult.push(this.extensions); + return toListResult; + } + + static fromList(list: Array): FileTypes { + let pigeonResult = new FileTypes(); + let mimeTypes: ESObject = list[0]; + pigeonResult.setMimeTypes(mimeTypes as Array); + let extensions: ESObject = list[1]; + pigeonResult.setExtensions(extensions as Array); + return pigeonResult; + } +} + + +export interface Result { + success(result: T): void; + error(error: Error): void; +} + +export class FileSelectorApiCodec extends StandardMessageCodec { + public static INSTANCE = new FileSelectorApiCodec(); + + readValueOfType(type: number, buffer: ByteBuffer): ESObject { + switch (type) { + case 128: + let res0 = FileResponse.fromList(super.readValue(buffer) as Array); + return res0; + case 129: + let vur: ESObject = super.readValue(buffer) + let res1 = FileTypes.fromList(vur as Array); + return res1; + default: + let res2: ESObject = super.readValueOfType(type, buffer); + return res2; + } + } + + writeValue(stream: ByteBuffer, value: ESObject): ESObject { + if (value instanceof FileResponse) { + stream.writeInt8(128); + return this.writeValue(stream, (value as FileResponse).toList()); + } else if (value instanceof FileTypes) { + stream.writeInt8(129); + return this.writeValue(stream, (value as FileTypes).toList()); + } else { + return super.writeValue(stream, value); + } + } + } diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/ets/pages/Index.ets b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 00000000..1125f9fd --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import common from '@ohos.app.ability.common'; +import { FlutterPage } from '@ohos/flutter_ohos' + +let storage = LocalStorage.getShared() +const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' + +@Entry(storage) +@Component +struct Index { + private context = getContext(this) as common.UIAbilityContext + @LocalStorageLink('viewId') viewId: string = ""; + + build() { + Column() { + FlutterPage({ viewId: this.viewId }) + } + } + + onBackPress(): boolean { + this.context.eventHub.emit(EVENT_BACK_PRESS) + return true + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets new file mode 100644 index 00000000..e749ab26 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets @@ -0,0 +1,30 @@ +import { FlutterEngine, Log } from '@ohos/flutter_ohos'; +import IntegrationTestPlugin from 'integration_test'; +import PathProviderPlugin from 'path_provider_ohos'; +import VideoPlayerPlugin from 'video_player_ohos'; + +/** + * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Ohos platform. + */ + +const TAG = "GeneratedPluginRegistrant"; + +export class GeneratedPluginRegistrant { + + static registerWith(flutterEngine: FlutterEngine) { + try { + flutterEngine.getPlugins()?.add(new IntegrationTestPlugin()); + flutterEngine.getPlugins()?.add(new PathProviderPlugin()); + flutterEngine.getPlugins()?.add(new VideoPlayerPlugin()); + } catch (e) { + Log.e( + TAG, + "Tried to register plugins with FlutterEngine (" + + flutterEngine + + ") failed."); + Log.e(TAG, "Received exception while registering", e); + } + } +} diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/color.json b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 00000000..3c712962 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/media/icon.png b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 Date: Wed, 13 Nov 2024 19:25:34 +0800 Subject: [PATCH 11/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4video=5Fplayer?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../ohos/.gitignore | 17 +++++ .../resources/base/element/string.json | 8 +++ .../ohos/entry/.gitignore | 7 ++ .../ohos/entry/oh-package.json5 | 14 ++++ .../ohos/entry/src/main/module.json5 | 62 ++++++++++++++++++ .../main/resources/base/element/string.json | 16 +++++ .../resources/base/profile/main_pages.json | 5 ++ .../main/resources/en_US/element/string.json | 16 +++++ .../main/resources/zh_CN/element/string.json | 16 +++++ .../entry/src/ohosTest/ets/test/List.test.ets | 20 ++++++ .../ohosTest/ets/testability/TestAbility.ets | 63 ++++++++++++++++++ .../ohosTest/ets/testability/pages/Index.ets | 49 ++++++++++++++ .../ets/testrunner/OpenHarmonyTestRunner.ts | 64 +++++++++++++++++++ .../ohos/entry/src/ohosTest/module.json5 | 51 +++++++++++++++ .../resources/base/element/string.json | 16 +++++ .../resources/base/profile/test_pages.json | 5 ++ .../ohos/oh-package.json5 | 19 ++++++ 17 files changed, 448 insertions(+) create mode 100644 ohos/automatic_video_player_test/ohos/.gitignore create mode 100644 ohos/automatic_video_player_test/ohos/AppScope/resources/base/element/string.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/.gitignore create mode 100644 ohos/automatic_video_player_test/ohos/entry/oh-package.json5 create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/module.json5 create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/string.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/profile/main_pages.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/resources/en_US/element/string.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/main/resources/zh_CN/element/string.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/module.json5 create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/element/string.json create mode 100644 ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json create mode 100644 ohos/automatic_video_player_test/ohos/oh-package.json5 diff --git a/ohos/automatic_video_player_test/ohos/.gitignore b/ohos/automatic_video_player_test/ohos/.gitignore new file mode 100644 index 00000000..6f7b4f89 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/.gitignore @@ -0,0 +1,17 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test + +/entry/libs/arm64-v8a/libflutter.so +/entry/src/main/resources/rawfile/flutter_assets +**.har +**/oh-package-lock.json5 +BuildProfile.ets diff --git a/ohos/automatic_video_player_test/ohos/AppScope/resources/base/element/string.json b/ohos/automatic_video_player_test/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 00000000..810f4a36 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "example" + } + ] +} diff --git a/ohos/automatic_video_player_test/ohos/entry/.gitignore b/ohos/automatic_video_player_test/ohos/entry/.gitignore new file mode 100644 index 00000000..2795a1c5 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/.gitignore @@ -0,0 +1,7 @@ + +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/oh-package.json5 b/ohos/automatic_video_player_test/ohos/entry/oh-package.json5 new file mode 100644 index 00000000..5cfbc705 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/flutter_ohos": "file:../har/flutter.har", + "video_player_ohos": "file:../har/video_player_ohos.har", + "integration_test": "file:../har/integration_test.har", + "path_provider_ohos": "file:../har/path_provider_ohos.har" + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/module.json5 b/ohos/automatic_video_player_test/ohos/entry/src/main/module.json5 new file mode 100644 index 00000000..1aeeb2da --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/module.json5 @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + ] + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/string.json b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000..061cdb87 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "video_player" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/profile/main_pages.json b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 00000000..1898d94f --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/resources/en_US/element/string.json b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 00000000..061cdb87 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "video_player" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/main/resources/zh_CN/element/string.json b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 00000000..db2c301c --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "video_player" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/test/List.test.ets b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 00000000..f4140030 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 00000000..4ca645e6 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; + +export default class TestAbility extends UIAbility { + onCreate(want, launchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 00000000..cef0447c --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 00000000..1def08f2 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/module.json5 b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 00000000..fab77ce2 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/element/string.json b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 00000000..65d8fa5a --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 00000000..b7e7343c --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/ohos/automatic_video_player_test/ohos/oh-package.json5 b/ohos/automatic_video_player_test/ohos/oh-package.json5 new file mode 100644 index 00000000..5e2eccf5 --- /dev/null +++ b/ohos/automatic_video_player_test/ohos/oh-package.json5 @@ -0,0 +1,19 @@ +{ + "modelVersion": "5.0.0", + "name": "apptemplate", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/hypium": "1.0.6" + }, + "overrides": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "video_player_ohos": "file:./har/video_player_ohos.har", + "@ohos/flutter_module": "file:./entry", + "integration_test": "file:./har/integration_test.har", + "path_provider_ohos": "file:./har/path_provider_ohos.har" + } +} \ No newline at end of file -- Gitee From 05648b765cb90ffebc153ffeb3bd2902a6132813 Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:26:53 +0800 Subject: [PATCH 12/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4video=5Fplayer?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- ohos/automatic_video_player_test/.metadata | 43 ++ ohos/automatic_video_player_test/README.md | 16 + .../analysis_options.yaml | 42 ++ .../coverage/lcov.info | 0 .../integration_test/video_player_test.dart | 467 +++++++++++++++ .../lib/fileselector/file_selector.dart | 73 +++ .../lib/fileselector/file_selector_api.dart | 142 +++++ .../lib/fileselector/x_type_group.dart | 86 +++ .../automatic_video_player_test/lib/main.dart | 343 +++++++++++ .../lib/mini_controller.dart | 566 ++++++++++++++++++ ohos/automatic_video_player_test/pubspec.yaml | 98 +++ ohos/automatic_video_player_test/result.json | 171 ++++++ .../test/widget_test.dart | 38 ++ .../test_driver/app.dart | 13 + .../test_driver/app_test.dart | 0 .../test_driver/integration_test.dart | 3 + 16 files changed, 2101 insertions(+) create mode 100644 ohos/automatic_video_player_test/.metadata create mode 100644 ohos/automatic_video_player_test/README.md create mode 100644 ohos/automatic_video_player_test/analysis_options.yaml create mode 100644 ohos/automatic_video_player_test/coverage/lcov.info create mode 100644 ohos/automatic_video_player_test/integration_test/video_player_test.dart create mode 100644 ohos/automatic_video_player_test/lib/fileselector/file_selector.dart create mode 100644 ohos/automatic_video_player_test/lib/fileselector/file_selector_api.dart create mode 100644 ohos/automatic_video_player_test/lib/fileselector/x_type_group.dart create mode 100644 ohos/automatic_video_player_test/lib/main.dart create mode 100644 ohos/automatic_video_player_test/lib/mini_controller.dart create mode 100644 ohos/automatic_video_player_test/pubspec.yaml create mode 100644 ohos/automatic_video_player_test/result.json create mode 100644 ohos/automatic_video_player_test/test/widget_test.dart create mode 100644 ohos/automatic_video_player_test/test_driver/app.dart create mode 100644 ohos/automatic_video_player_test/test_driver/app_test.dart create mode 100644 ohos/automatic_video_player_test/test_driver/integration_test.dart diff --git a/ohos/automatic_video_player_test/.metadata b/ohos/automatic_video_player_test/.metadata new file mode 100644 index 00000000..a8cbceb3 --- /dev/null +++ b/ohos/automatic_video_player_test/.metadata @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f63170e391f0d2349c3f6b4d6cabf856f0f7b76f + channel: unknown + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f63170e391f0d2349c3f6b4d6cabf856f0f7b76f + base_revision: f63170e391f0d2349c3f6b4d6cabf856f0f7b76f + - platform: ohos + create_revision: f63170e391f0d2349c3f6b4d6cabf856f0f7b76f + base_revision: f63170e391f0d2349c3f6b4d6cabf856f0f7b76f + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/ohos/automatic_video_player_test/README.md b/ohos/automatic_video_player_test/README.md new file mode 100644 index 00000000..2b3fce4c --- /dev/null +++ b/ohos/automatic_video_player_test/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/ohos/automatic_video_player_test/analysis_options.yaml b/ohos/automatic_video_player_test/analysis_options.yaml new file mode 100644 index 00000000..7814013c --- /dev/null +++ b/ohos/automatic_video_player_test/analysis_options.yaml @@ -0,0 +1,42 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/ohos/automatic_video_player_test/coverage/lcov.info b/ohos/automatic_video_player_test/coverage/lcov.info new file mode 100644 index 00000000..e69de29b diff --git a/ohos/automatic_video_player_test/integration_test/video_player_test.dart b/ohos/automatic_video_player_test/integration_test/video_player_test.dart new file mode 100644 index 00000000..00b1e4e1 --- /dev/null +++ b/ohos/automatic_video_player_test/integration_test/video_player_test.dart @@ -0,0 +1,467 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:ffi'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:video_player/video_player.dart'; + +const Duration _playDuration = Duration(seconds: 1); + +// Use WebM for web to allow CI to use Chromium. +const String _videoAssetKey = + kIsWeb ? 'assets/Butterfly-209.webm' : 'assets/Butterfly-209.mp4'; + +// Returns the URL to load an asset from this example app as a network source. +// +// TODO(stuartmorgan): Convert this to a local `HttpServer` that vends the +// assets directly, https://github.com/flutter/flutter/issues/95420 +String getUrlForAssetAsNetworkSource(String assetKey) { + return 'https://github.com/flutter/packages/blob/' + // This hash can be rolled forward to pick up newly-added assets. + '2e1673307ff7454aff40b47024eaed49a9e77e81' + '/packages/video_player/video_player/example/' + '$assetKey' + '?raw=true'; +} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + late VideoPlayerController controller; + tearDown(() async => controller.dispose()); + + group('asset videos', () { + setUp(() { + controller = VideoPlayerController.asset(_videoAssetKey); + }); + + testWidgets('asset videos - can be initialized', (WidgetTester tester) async { + await controller.initialize(); + + print("[====asset videos - can be initialized-1-" + "${controller.value.isInitialized == true ? "success" : "failed"}]:"); + expect(controller.value.isInitialized, true); + + print("[====asset videos - can be initialized-2-" + "${controller.value.position == Duration.zero ? "success" : "failed"}]:"); + expect(controller.value.position, Duration.zero); + + print("[====asset videos - can be initialized-3-" + "${controller.value.isPlaying == false ? "success" : "failed"}]:"); + expect(controller.value.isPlaying, false); + // The WebM version has a slightly different duration than the MP4. + + print("[====asset videos - can be initialized-4-" + "${controller.value.duration == const Duration(seconds: 7, milliseconds: kIsWeb ? 544 : 540) + ? "success" : "failed"}]:"); + expect(controller.value.duration, + const Duration(seconds: 7, milliseconds: kIsWeb ? 544 : 540)); + }); + + testWidgets( + 'asset videos - live stream duration != 0', + (WidgetTester tester) async { + final VideoPlayerController networkController = + VideoPlayerController.networkUrl( + Uri.parse( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8'), + ); + await networkController.initialize(); + + print("[====live stream duration != 0-1-" + "${networkController.value.isInitialized == true + ? "success" : "failed"}]:"); + expect(networkController.value.isInitialized, true); + // Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown + // See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration-- + + print("[====live stream duration != 0 -2-" + "${networkController.value.duration == (Duration duration) => duration != Duration.zero + ? "success" : "failed"}]:"); + + }, + skip: true, + ); + + testWidgets( + 'asset videos - can be played', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + + await controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(controller.value.isPlaying, true); + print("[====asset videos - can be played -1- " + "${controller.value.isPlaying + ? "success" : "failed"}]:"); + + await controller.setVolume(0.5); + print("[====asset videos - can be played -2- " + "${controller.value.volume == 0.5 + ? "success" : "failed"}]:"); + + sleep(const Duration(milliseconds: 500)); + expect(controller.value.position, + (Duration position) => position > Duration.zero); + print("[====asset videos - can be played -3- " + "${controller.value.position > Duration.zero + ? "success" : "failed"}]:"); + }, + ); + + testWidgets( + 'asset videos - can seek', + (WidgetTester tester) async { + await controller.initialize(); + + await controller.seekTo(const Duration(seconds: 3)); + + expect(controller.value.position, const Duration(seconds: 3)); + print("[====asset videos - can seek-" + "${controller.value.position == const Duration(seconds: 3) + ? "success" : "failed"}]:"); + }, + ); + + testWidgets( + 'asset videos - can be paused', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + + // Play for a second, then pause, and then wait a second. + await controller.play(); + await tester.pumpAndSettle(_playDuration); + await controller.pause(); + final Duration pausedPosition = controller.value.position; + await tester.pumpAndSettle(_playDuration); + + // Verify that we stopped playing after the pause. + expect(controller.value.isPlaying, false); + print("[====asset videos - can be paused - 1 -" + "${controller.value.isPlaying == false ? "success" : "failed"}]:"); + + expect(controller.value.position, pausedPosition); + print("[====asset videos - can be paused - 2 -" + "${controller.value.position == pausedPosition ? "success" : "failed"}]:"); + }, + ); + + testWidgets( + 'asset videos - can be loop play', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + + // Play for a second, then pause, and then wait a second. + await controller.play(); + await tester.pumpAndSettle(_playDuration); + final Duration durationLength = controller.value.duration; + print(durationLength); + await controller.setLooping(true); + sleep(durationLength * 1.6); + // final Duration pausedPosition = controller.value.position; + await tester.pumpAndSettle(_playDuration); + + print("[====asset videos - can be loop play - " + "${controller.value.isPlaying == true ? "success" : "failed"}]:"); + }, + ); + + testWidgets( + 'asset videos - can set backplayspeed', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + + // Play for a second, then pause, and then wait a second. + await controller.play(); + controller.setLooping(false); + await tester.pumpAndSettle(_playDuration); + await controller.setPlaybackSpeed(1.75 as double); + // final Duration pausedPosition = controller.value.position; + await tester.pumpAndSettle(_playDuration); + final Duration durationLength = controller.value.duration; + sleep(durationLength * 0.9); + final Duration postion = controller.value.position; + // Verify that we stopped playing after the pause. + print("====can set backplayspeed -" + "${controller.value.isPlaying == false ? "success" : "failed"}:"); + }, + ); + + testWidgets( + 'stay paused when seeking after video completed', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + final Duration tenMillisBeforeEnd = + controller.value.duration - const Duration(milliseconds: 10); + await controller.seekTo(tenMillisBeforeEnd); + await controller.play(); + await tester.pumpAndSettle(_playDuration); + expect(controller.value.isPlaying, false); + expect(controller.value.position, controller.value.duration); + + await controller.seekTo(tenMillisBeforeEnd); + await tester.pumpAndSettle(_playDuration); + + expect(controller.value.isPlaying, false); + print("[====stay paused when seeking after video completed - 1 -" + "${controller.value.isPlaying == false ? "success" : "failed"}]:"); + expect(controller.value.position, tenMillisBeforeEnd); + print("[====stay paused when seeking after video completed - 2 -" + "${controller.value.position == tenMillisBeforeEnd ? "success" : "failed"}]:"); + }, + // Flaky on web: https://github.com/flutter/flutter/issues/130147 + skip: kIsWeb, + ); + + testWidgets( + 'do not exceed duration on play after video completed', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + await controller.seekTo( + controller.value.duration - const Duration(milliseconds: 10)); + await controller.play(); + await tester.pumpAndSettle(_playDuration); + expect(controller.value.isPlaying, false); + expect(controller.value.position, controller.value.duration); + + await controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(controller.value.position, + lessThanOrEqualTo(controller.value.duration)); + print("[====do not exceed duration on play after video completed - " + "${controller.value.position < controller.value.duration ? "success" : "failed"}]:"); + }, + ); + + testWidgets('test video player view with local asset', + (WidgetTester tester) async { + Future started() async { + await controller.initialize(); + await controller.play(); + return true; + } + + await tester.pumpWidget(Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: FutureBuilder( + future: started(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.data ?? false) { + return AspectRatio( + aspectRatio: controller.value.aspectRatio, + child: VideoPlayer(controller), + ); + } else { + return const Text('waiting for video to load'); + } + }, + ), + ), + ), + )); + + await tester.pumpAndSettle(); + expect(controller.value.isPlaying, true); + print("[====test video player view with local asset - " + "${controller.value.isPlaying ? "success" : "failed"}]:"); + }, + skip: kIsWeb || // Web does not support local assets. + // Extremely flaky on iOS: https://github.com/flutter/flutter/issues/86915 + defaultTargetPlatform == TargetPlatform.iOS); + }); + + group('file-based videos', () { + setUp(() async { + // Load the data from the asset. + final String tempDir = (await getTemporaryDirectory()).path; + final ByteData bytes = await rootBundle.load(_videoAssetKey); + + // Write it to a file to use as a source. + final String filename = _videoAssetKey.split('/').last; + final File file = File('$tempDir/$filename'); + await file.writeAsBytes(bytes.buffer.asInt8List()); + + controller = VideoPlayerController.file(file); + }); + + testWidgets('test video player using static file() method as constructor', + (WidgetTester tester) async { + await controller.initialize(); + + await controller.play(); + expect(controller.value.isPlaying, true); + + await controller.pause(); + expect(controller.value.isPlaying, false); + print("[====test video player using static file() method as constructor- " + "${!controller.value.isPlaying ? "success" : "failed"}]:"); + }, skip: kIsWeb); + }); + + group('network videos', () { + setUp(() { + controller = VideoPlayerController.networkUrl( + Uri.parse(getUrlForAssetAsNetworkSource(_videoAssetKey))); + }); + + testWidgets( + 'reports buffering status', + (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + final Completer started = Completer(); + final Completer ended = Completer(); + controller.addListener(() { + if (!started.isCompleted && controller.value.isBuffering) { + started.complete(); + } + if (started.isCompleted && + !controller.value.isBuffering && + !ended.isCompleted) { + ended.complete(); + } + }); + + await controller.play(); + await controller.seekTo(const Duration(seconds: 5)); + await tester.pumpAndSettle(_playDuration); + await controller.pause(); + + expect(controller.value.isPlaying, false); + print("[====reports buffering status - 1 - " + "${!controller.value.isPlaying ? "success" : "failed"}]:"); + + expect(controller.value.position, + (Duration position) => position > Duration.zero); + print("[====reports buffering status - 2 - " + "${controller.value.position == (Duration position) => position > Duration.zero ? "success" : "failed"}]:"); + + await expectLater(started.future, completes); + await expectLater(ended.future, completes); + }, + skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android), + ); + }); + + // Audio playback is tested to prevent accidental regression, + // but could be removed in the future. + group('asset audios', () { + setUp(() { + controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); + }); + + testWidgets('asset audios - can be initialized', (WidgetTester tester) async { + await controller.initialize(); + + expect(controller.value.isInitialized, true); + print("[====asset audios - can be initialized - 1 - " + "${controller.value.isInitialized ? "success" : "failed"}]:"); + + expect(controller.value.position, Duration.zero); + print("[====asset audios - can be initialized - 2 - " + "${controller.value.position == Duration.zero ? "success" : "failed"}]:"); + + expect(controller.value.isPlaying, false); + print("[====asset audios - can be initialized - 3 - " + "${controller.value.isPlaying == false ? "success" : "failed"}]:"); + // Due to the duration calculation accuracy between platforms, + // the milliseconds on Web will be a slightly different from natives. + // The audio was made with 44100 Hz, 192 Kbps CBR, and 32 bits. + expect( + controller.value.duration, + const Duration(seconds: 7, milliseconds: 540), + ); + print("[====asset audios - can be initialized - 4 - " + "${controller.value.duration == const Duration(seconds: 7, milliseconds: 540) + ? "success" : "failed"}]:"); + }); + + testWidgets('asset audios - can be played', (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + + await controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(controller.value.isPlaying, true); + print("[====asset audios - can be played - 1 - " + "${controller.value.isPlaying + ? "success" : "failed"}]:"); + expect( + controller.value.position, + (Duration position) => position > Duration.zero, + ); + print("[====asset audios - can be played - 2 - " + "${controller.value.position > Duration.zero + ? "success" : "failed"}]:"); + }); + + testWidgets('asset audios - can seek', (WidgetTester tester) async { + await controller.initialize(); + await controller.seekTo(const Duration(seconds: 3)); + + expect(controller.value.position, const Duration(seconds: 3)); + print("[====asset audios - can seek - " + "${controller.value.position == const Duration(seconds: 3) + ? "success" : "failed"}]:"); + }); + + testWidgets('asset audios - can be paused', (WidgetTester tester) async { + await controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await controller.setVolume(0); + + // Play for a second, then pause, and then wait a second. + await controller.play(); + await tester.pumpAndSettle(_playDuration); + await controller.pause(); + final Duration pausedPosition = controller.value.position; + await tester.pumpAndSettle(_playDuration); + + // Verify that we stopped playing after the pause. + expect(controller.value.isPlaying, false); + print("[====asset audios - can be paused - 1 -" + "${!controller.value.isPlaying + ? "success" : "failed"}]:"); + expect(controller.value.position, pausedPosition); + print("[====asset audios - can be played - 2 -" + "${controller.value.position == pausedPosition + ? "success" : "failed"}]:"); + }); + }); +} diff --git a/ohos/automatic_video_player_test/lib/fileselector/file_selector.dart b/ohos/automatic_video_player_test/lib/fileselector/file_selector.dart new file mode 100644 index 00000000..4b717e12 --- /dev/null +++ b/ohos/automatic_video_player_test/lib/fileselector/file_selector.dart @@ -0,0 +1,73 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'package:example/fileselector/x_type_group.dart'; +import 'file_selector_api.dart'; + +class FileSelector { + FileSelector({ FileSelectorApi? api}) + : _api = api ?? FileSelectorApi(); + + final FileSelectorApi _api; + + /// Registers this class as the implementation of the file_selector platform interface. + // static void registerWith() { + // FileSelectorPlatform.instance = FileSelectorAndroid(); + // } + + @override + Future openFile({ + List? acceptedTypeGroups, + String? confirmButtonText, + }) async { + final FileResponse? file = await _api.openFile( + _fileTypesFromTypeGroups(acceptedTypeGroups), + ); + print("openfile#"); + print(file?.fd); + return file?.fd; + } + + + + FileTypes _fileTypesFromTypeGroups(List? typeGroups) { + if (typeGroups == null) { + return FileTypes(extensions: [], mimeTypes: []); + } + + final Set mimeTypes = {}; + final Set extensions = {}; + + for (final XTypeGroup group in typeGroups) { + if (!group.allowsAny && + group.mimeTypes == null && + group.extensions == null) { + throw ArgumentError( + 'Provided type group $group does not allow all files, but does not ' + 'set any of the Android supported filter categories. At least one of ' + '"extensions" or "mimeTypes" must be non-empty for Android.', + ); + } + + mimeTypes.addAll(group.mimeTypes ?? {}); + extensions.addAll(group.extensions ?? {}); + } + + return FileTypes( + mimeTypes: mimeTypes.toList(), + extensions: extensions.toList(), + ); + } +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/lib/fileselector/file_selector_api.dart b/ohos/automatic_video_player_test/lib/fileselector/file_selector_api.dart new file mode 100644 index 00000000..bed8e29f --- /dev/null +++ b/ohos/automatic_video_player_test/lib/fileselector/file_selector_api.dart @@ -0,0 +1,142 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'dart:async'; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class FileResponse { + FileResponse({ + required this.path, + this.name, + required this.fd, + }); + + String path; + + String? name; + + int fd; + + Object encode() { + return [ + path, + name, + fd, + ]; + } + + static FileResponse decode(Object result) { + result as List; + return FileResponse( + path: result[0]! as String, + name: result[1] as String?, + fd: result[2]! as int, + ); + } +} + +class FileTypes { + FileTypes({ + required this.mimeTypes, + required this.extensions, + }); + + List mimeTypes; + + List extensions; + + Object encode() { + return [ + mimeTypes, + extensions, + ]; + } + + static FileTypes decode(Object result) { + result as List; + return FileTypes( + mimeTypes: (result[0] as List?)!.cast(), + extensions: (result[1] as List?)!.cast(), + ); + } +} + +class _FileSelectorApiCodec extends StandardMessageCodec { + const _FileSelectorApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is FileResponse) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is FileTypes) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return FileResponse.decode(readValue(buffer)!); + case 129: + return FileTypes.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// An API to call to native code to select files or directories. +class FileSelectorApi { + /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + FileSelectorApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _FileSelectorApiCodec(); + + /// Opens a file dialog for loading files and returns a file path. + /// + /// Returns `null` if user cancels the operation. + Future openFile(FileTypes arg_allowedTypes) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.FileSelectorApi.openFile', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_allowedTypes]) + as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as FileResponse?); + } + } + +} diff --git a/ohos/automatic_video_player_test/lib/fileselector/x_type_group.dart b/ohos/automatic_video_player_test/lib/fileselector/x_type_group.dart new file mode 100644 index 00000000..7f081d97 --- /dev/null +++ b/ohos/automatic_video_player_test/lib/fileselector/x_type_group.dart @@ -0,0 +1,86 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'package:flutter/foundation.dart' show immutable; + +/// A set of allowed XTypes. +@immutable +class XTypeGroup { + /// Creates a new group with the given label and file extensions. + /// + /// A group with none of the type options provided indicates that any type is + /// allowed. + const XTypeGroup({ + this.label, + List? extensions, + this.mimeTypes, + List? uniformTypeIdentifiers, + this.webWildCards, + @Deprecated('Use uniformTypeIdentifiers instead') List? macUTIs, + }) : _extensions = extensions, + assert(uniformTypeIdentifiers == null || macUTIs == null, + 'Only one of uniformTypeIdentifiers or macUTIs can be non-null'), + uniformTypeIdentifiers = uniformTypeIdentifiers ?? macUTIs; + + /// The 'name' or reference to this group of types. + final String? label; + + /// The MIME types for this group. + final List? mimeTypes; + + /// The uniform type identifiers for this group + final List? uniformTypeIdentifiers; + + /// The web wild cards for this group (ex: image/*, video/*). + final List? webWildCards; + + final List? _extensions; + + /// The extensions for this group. + List? get extensions { + return _removeLeadingDots(_extensions); + } + + /// Converts this object into a JSON formatted object. + Map toJSON() { + return { + 'label': label, + 'extensions': extensions, + 'mimeTypes': mimeTypes, + 'uniformTypeIdentifiers': uniformTypeIdentifiers, + 'webWildCards': webWildCards, + // This is kept for backwards compatibility with anything that was + // relying on it, including implementers of `MethodChannelFileSelector` + // (since toJSON is used in the method channel parameter serialization). + 'macUTIs': uniformTypeIdentifiers, + }; + } + + /// True if this type group should allow any file. + bool get allowsAny { + return (extensions?.isEmpty ?? true) && + (mimeTypes?.isEmpty ?? true) && + (uniformTypeIdentifiers?.isEmpty ?? true) && + (webWildCards?.isEmpty ?? true); + } + + /// Returns the list of uniform type identifiers for this group + @Deprecated('Use uniformTypeIdentifiers instead') + List? get macUTIs => uniformTypeIdentifiers; + + static List? _removeLeadingDots(List? exts) => exts + ?.map((String ext) => ext.startsWith('.') ? ext.substring(1) : ext) + .toList(); +} diff --git a/ohos/automatic_video_player_test/lib/main.dart b/ohos/automatic_video_player_test/lib/main.dart new file mode 100644 index 00000000..73a0feb3 --- /dev/null +++ b/ohos/automatic_video_player_test/lib/main.dart @@ -0,0 +1,343 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'fileselector/file_selector.dart'; +import 'fileselector/x_type_group.dart'; +import 'mini_controller.dart'; + +void main() { + runApp( + MaterialApp( + home: _App(), + ), + ); +} + +class _App extends StatelessWidget { + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 3, + child: Scaffold( + key: const ValueKey('home_page'), + appBar: AppBar( + title: const Text('Video player example'), + bottom: const TabBar( + isScrollable: true, + tabs: [ + Tab( + icon: Icon(Icons.insert_drive_file), + text: 'Asset' + ), + Tab( + icon: Icon(Icons.cloud), + text: 'Remote', + ), + Tab( + icon: Icon(Icons.file_open), + text: 'LocalFile' + ), + ], + ), + ), + body: TabBarView( + children: [ + _ButterFlyAssetVideo(), + _BumbleBeeRemoteVideo(), + _LocalFileVideo(), + ], + ), + ), + ); + } +} + +class _ButterFlyAssetVideo extends StatefulWidget { + @override + _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); +} + +class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { + late MiniController _controller; + + @override + void initState() { + super.initState(); + _controller = MiniController.asset('assets/video1.mp4'); + + _controller.addListener(() { + setState(() {}); + }); + _controller.initialize().then((_) => setState(() {})); + _controller.play(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20.0), + ), + const Text('With assets mp4'), + Container( + padding: const EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _LocalFileVideo extends StatefulWidget { + @override + _LocalFileVideoState createState() => _LocalFileVideoState(); +} + +class _LocalFileVideoState extends State<_LocalFileVideo> { + late MiniController _controller; + int? fileFd; + + Future selectorFile() async { + print("selectorFile"); + const XTypeGroup typeGroup = XTypeGroup( + label: 'video', + extensions: ['mp4'], + uniformTypeIdentifiers: ['public.video'], + ); + final FileSelector instance = FileSelector(); + fileFd = await instance + .openFile(acceptedTypeGroups: [typeGroup]); + } + + @override + void initState() { + super.initState(); + _controller = MiniController.file(0); + } + + void getFileFd() { + print("getFileFd"); + selectorFile().then((value) { + _controller = MiniController.file(fileFd ?? 0); + _controller.addListener(() { + setState(() {}); + }); + _controller.initialize().whenComplete(() { + _controller.play(); + }); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ButtonStyle style = ElevatedButton.styleFrom( + foregroundColor: Colors.blue, + backgroundColor: Colors.white, + ); + return SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20.0), + ), + const Text('With local file mp4'), + Container( + padding: const EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller), + ], + ), + ), + ), + Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: style, + child: const Text('Open a video file'), + onPressed: () => {getFileFd()}, + ), + ], + ), + ), + ], + ), + ); + } +} + +class _BumbleBeeRemoteVideo extends StatefulWidget { + @override + _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); +} + +class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { + late MiniController _controller; + + @override + void initState() { + super.initState(); + _controller = MiniController.network( + 'https://media.w3.org/2010/05/sintel/trailer.mp4', + ); + + _controller.addListener(() { + setState(() {}); + }); + _controller.initialize().whenComplete(() => _controller.play()); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Container(padding: const EdgeInsets.only(top: 20.0)), + const Text('With remote mp4'), + Container( + padding: const EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _ControlsOverlay extends StatelessWidget { + const _ControlsOverlay({required this.controller}); + + static const List _examplePlaybackRates = [ + 0.75, + 1.0, + 1.25, + 1.75, + 2.0, + ]; + + final MiniController controller; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AnimatedSwitcher( + duration: const Duration(milliseconds: 50), + reverseDuration: const Duration(milliseconds: 200), + child: controller.value.isPlaying + ? const SizedBox.shrink() + : Container( + color: Colors.black26, + child: const Center( + child: Icon( + Icons.play_arrow, + color: Colors.white, + size: 100.0, + semanticLabel: 'Play', + ), + ), + ), + ), + GestureDetector( + onTap: () { + controller.value.isPlaying ? controller.pause() : controller.play(); + }, + ), + Align( + alignment: Alignment.topRight, + child: PopupMenuButton( + initialValue: controller.value.playbackSpeed, + tooltip: 'Playback speed', + onSelected: (double speed) { + controller.setPlaybackSpeed(speed); + }, + itemBuilder: (BuildContext context) { + return >[ + for (final double speed in _examplePlaybackRates) + PopupMenuItem( + value: speed, + child: Text('${speed}x'), + ) + ]; + }, + child: Padding( + padding: const EdgeInsets.symmetric( + // Using less vertical padding as the text is also longer + // horizontally, so it feels like it would need more spacing + // horizontally (matching the aspect ratio of the video). + vertical: 12, + horizontal: 16, + ), + child: Text('${controller.value.playbackSpeed}x'), + ), + ), + ), + ], + ); + } +} diff --git a/ohos/automatic_video_player_test/lib/mini_controller.dart b/ohos/automatic_video_player_test/lib/mini_controller.dart new file mode 100644 index 00000000..0a2b5305 --- /dev/null +++ b/ohos/automatic_video_player_test/lib/mini_controller.dart @@ -0,0 +1,566 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; + +VideoPlayerPlatform? _cachedPlatform; + +VideoPlayerPlatform get _platform { + if (_cachedPlatform == null) { + _cachedPlatform = VideoPlayerPlatform.instance; + _cachedPlatform!.init(); + } + return _cachedPlatform!; +} + +/// The duration, current position, buffering state, error state and settings +/// of a [MiniController]. +@immutable +class VideoPlayerValue { + /// Constructs a video with the given values. Only [duration] is required. The + /// rest will initialize with default values when unset. + const VideoPlayerValue({ + required this.duration, + this.size = Size.zero, + this.position = Duration.zero, + this.buffered = const [], + this.isInitialized = false, + this.isPlaying = false, + this.isBuffering = false, + this.playbackSpeed = 1.0, + this.errorDescription, + }); + + /// Returns an instance for a video that hasn't been loaded. + const VideoPlayerValue.uninitialized() + : this(duration: Duration.zero, isInitialized: false); + + /// Returns an instance with the given [errorDescription]. + const VideoPlayerValue.erroneous(String errorDescription) + : this( + duration: Duration.zero, + isInitialized: false, + errorDescription: errorDescription); + + /// The total duration of the video. + /// + /// The duration is [Duration.zero] if the video hasn't been initialized. + final Duration duration; + + /// The current playback position. + final Duration position; + + /// The currently buffered ranges. + final List buffered; + + /// True if the video is playing. False if it's paused. + final bool isPlaying; + + /// True if the video is currently buffering. + final bool isBuffering; + + /// The current speed of the playback. + final double playbackSpeed; + + /// A description of the error if present. + /// + /// If [hasError] is false this is `null`. + final String? errorDescription; + + /// The [size] of the currently loaded video. + final Size size; + + /// Indicates whether or not the video has been loaded and is ready to play. + final bool isInitialized; + + /// Indicates whether or not the video is in an error state. If this is true + /// [errorDescription] should have information about the problem. + bool get hasError => errorDescription != null; + + /// Returns [size.width] / [size.height]. + /// + /// Will return `1.0` if: + /// * [isInitialized] is `false` + /// * [size.width], or [size.height] is equal to `0.0` + /// * aspect ratio would be less than or equal to `0.0` + double get aspectRatio { + if (!isInitialized || size.width == 0 || size.height == 0) { + return 1.0; + } + final double aspectRatio = size.width / size.height; + if (aspectRatio <= 0) { + return 1.0; + } + return aspectRatio; + } + + /// Returns a new instance that has the same values as this current instance, + /// except for any overrides passed in as arguments to [copyWidth]. + VideoPlayerValue copyWith({ + Duration? duration, + Size? size, + Duration? position, + List? buffered, + bool? isInitialized, + bool? isPlaying, + bool? isBuffering, + double? playbackSpeed, + String? errorDescription, + }) { + return VideoPlayerValue( + duration: duration ?? this.duration, + size: size ?? this.size, + position: position ?? this.position, + buffered: buffered ?? this.buffered, + isInitialized: isInitialized ?? this.isInitialized, + isPlaying: isPlaying ?? this.isPlaying, + isBuffering: isBuffering ?? this.isBuffering, + playbackSpeed: playbackSpeed ?? this.playbackSpeed, + errorDescription: errorDescription ?? this.errorDescription, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is VideoPlayerValue && + runtimeType == other.runtimeType && + duration == other.duration && + position == other.position && + listEquals(buffered, other.buffered) && + isPlaying == other.isPlaying && + isBuffering == other.isBuffering && + playbackSpeed == other.playbackSpeed && + errorDescription == other.errorDescription && + size == other.size && + isInitialized == other.isInitialized; + + @override + int get hashCode => Object.hash( + duration, + position, + buffered, + isPlaying, + isBuffering, + playbackSpeed, + errorDescription, + size, + isInitialized, + ); +} + +/// A very minimal version of `VideoPlayerController` for running the example +/// without relying on `video_player`. +class MiniController extends ValueNotifier { + /// Constructs a [MiniController] playing a video from an asset. + /// + /// The name of the asset is given by the [dataSource] argument and must not be + /// null. The [package] argument must be non-null when the asset comes from a + /// package and null otherwise. + MiniController.asset(this.dataSource, {this.package}) + : dataSourceType = DataSourceType.asset, + super(const VideoPlayerValue(duration: Duration.zero)); + + /// Constructs a [MiniController] playing a video from obtained from + /// the network. + MiniController.network(this.dataSource) + : dataSourceType = DataSourceType.network, + package = null, + super(const VideoPlayerValue(duration: Duration.zero)); + + /// Constructs a [MiniController] playing a video from obtained from a file. + MiniController.file(int fileFd) + : dataSource = "fd://$fileFd", + dataSourceType = DataSourceType.file, + package = null, + super(const VideoPlayerValue(duration: Duration.zero)); + + /// The URI to the video file. This will be in different formats depending on + /// the [DataSourceType] of the original video. + final String dataSource; + + /// Describes the type of data source this [MiniController] + /// is constructed with. + final DataSourceType dataSourceType; + + /// Only set for [asset] videos. The package that the asset was loaded from. + final String? package; + + Timer? _timer; + Completer? _creatingCompleter; + StreamSubscription? _eventSubscription; + + /// The id of a texture that hasn't been initialized. + @visibleForTesting + static const int kUninitializedTextureId = -1; + int _textureId = kUninitializedTextureId; + + /// This is just exposed for testing. It shouldn't be used by anyone depending + /// on the plugin. + @visibleForTesting + int get textureId => _textureId; + + /// Attempts to open the given [dataSource] and load metadata about the video. + Future initialize() async { + _creatingCompleter = Completer(); + + late DataSource dataSourceDescription; + switch (dataSourceType) { + case DataSourceType.asset: + dataSourceDescription = DataSource( + sourceType: DataSourceType.asset, + asset: dataSource, + package: package, + ); + break; + case DataSourceType.network: + dataSourceDescription = DataSource( + sourceType: DataSourceType.network, + uri: dataSource, + ); + break; + case DataSourceType.file: + dataSourceDescription = DataSource( + sourceType: DataSourceType.file, + uri: dataSource, + ); + break; + } + + _textureId = (await _platform.create(dataSourceDescription)) ?? + kUninitializedTextureId; + _creatingCompleter!.complete(null); + final Completer initializingCompleter = Completer(); + + void eventListener(VideoEvent event) { + switch (event.eventType) { + case VideoEventType.initialized: + value = value.copyWith( + duration: event.duration, + size: event.size, + isInitialized: event.duration != null, + ); + initializingCompleter.complete(null); + _platform.setVolume(_textureId, 1.0); + _platform.setLooping(_textureId, true); + _applyPlayPause(); + break; + case VideoEventType.completed: + pause().then((void pauseResult) => seekTo(value.duration)); + break; + case VideoEventType.bufferingUpdate: + value = value.copyWith(buffered: event.buffered); + break; + case VideoEventType.bufferingStart: + value = value.copyWith(isBuffering: true); + break; + case VideoEventType.bufferingEnd: + value = value.copyWith(isBuffering: false); + break; + case VideoEventType.isPlayingStateUpdate: + value = value.copyWith(isPlaying: event.isPlaying); + break; + case VideoEventType.unknown: + break; + } + } + + void errorListener(Object obj) { + final PlatformException e = obj as PlatformException; + value = VideoPlayerValue.erroneous(e.message!); + _timer?.cancel(); + if (!initializingCompleter.isCompleted) { + initializingCompleter.completeError(obj); + } + } + + _eventSubscription = _platform + .videoEventsFor(_textureId) + .listen(eventListener, onError: errorListener); + return initializingCompleter.future; + } + + @override + Future dispose() async { + if (_creatingCompleter != null) { + await _creatingCompleter!.future; + _timer?.cancel(); + await _eventSubscription?.cancel(); + await _platform.dispose(_textureId); + } + super.dispose(); + } + + /// Starts playing the video. + Future play() async { + value = value.copyWith(isPlaying: true); + await _applyPlayPause(); + } + + /// Pauses the video. + Future pause() async { + value = value.copyWith(isPlaying: false); + await _applyPlayPause(); + } + + Future _applyPlayPause() async { + _timer?.cancel(); + if (value.isPlaying) { + await _platform.play(_textureId); + + _timer = Timer.periodic( + const Duration(milliseconds: 500), + (Timer timer) async { + final Duration? newPosition = await position; + if (newPosition == null) { + return; + } + _updatePosition(newPosition); + }, + ); + await _applyPlaybackSpeed(); + } else { + await _platform.pause(_textureId); + } + } + + Future _applyPlaybackSpeed() async { + if (value.isPlaying) { + await _platform.setPlaybackSpeed( + _textureId, + value.playbackSpeed, + ); + } + } + + /// The position in the current video. + Future get position async { + return _platform.getPosition(_textureId); + } + + /// Sets the video's current timestamp to be at [position]. + Future seekTo(Duration position) async { + if (position > value.duration) { + position = value.duration; + } else if (position < Duration.zero) { + position = Duration.zero; + } + await _platform.seekTo(_textureId, position); + _updatePosition(position); + } + + /// Sets the playback speed. + Future setPlaybackSpeed(double speed) async { + value = value.copyWith(playbackSpeed: speed); + await _applyPlaybackSpeed(); + } + + void _updatePosition(Duration position) { + value = value.copyWith(position: position); + } +} + +/// Widget that displays the video controlled by [controller]. +class VideoPlayer extends StatefulWidget { + /// Uses the given [controller] for all video rendered in this widget. + const VideoPlayer(this.controller, {super.key}); + + /// The [MiniController] responsible for the video being rendered in + /// this widget. + final MiniController controller; + + @override + State createState() => _VideoPlayerState(); +} + +class _VideoPlayerState extends State { + _VideoPlayerState() { + _listener = () { + final int newTextureId = widget.controller.textureId; + if (newTextureId != _textureId) { + setState(() { + _textureId = newTextureId; + }); + } + }; + } + + late VoidCallback _listener; + + late int _textureId; + + @override + void initState() { + super.initState(); + _textureId = widget.controller.textureId; + // Need to listen for initialization events since the actual texture ID + // becomes available after asynchronous initialization finishes. + widget.controller.addListener(_listener); + } + + @override + void didUpdateWidget(VideoPlayer oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.controller.removeListener(_listener); + _textureId = widget.controller.textureId; + widget.controller.addListener(_listener); + } + + @override + void deactivate() { + super.deactivate(); + widget.controller.removeListener(_listener); + } + + @override + Widget build(BuildContext context) { + return _textureId == MiniController.kUninitializedTextureId + ? Container() + : _platform.buildView(_textureId); + } +} + +class _VideoScrubber extends StatefulWidget { + const _VideoScrubber({ + required this.child, + required this.controller, + }); + + final Widget child; + final MiniController controller; + + @override + _VideoScrubberState createState() => _VideoScrubberState(); +} + +class _VideoScrubberState extends State<_VideoScrubber> { + MiniController get controller => widget.controller; + + @override + Widget build(BuildContext context) { + void seekToRelativePosition(Offset globalPosition) { + final RenderBox box = context.findRenderObject()! as RenderBox; + final Offset tapPos = box.globalToLocal(globalPosition); + final double relative = tapPos.dx / box.size.width; + final Duration position = controller.value.duration * relative; + controller.seekTo(position); + } + + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: widget.child, + onTapDown: (TapDownDetails details) { + if (controller.value.isInitialized) { + seekToRelativePosition(details.globalPosition); + } + }, + ); + } +} + +/// Displays the play/buffering status of the video controlled by [controller]. +class VideoProgressIndicator extends StatefulWidget { + /// Construct an instance that displays the play/buffering status of the video + /// controlled by [controller]. + const VideoProgressIndicator(this.controller, {super.key}); + + /// The [MiniController] that actually associates a video with this + /// widget. + final MiniController controller; + + @override + State createState() => _VideoProgressIndicatorState(); +} + +class _VideoProgressIndicatorState extends State { + _VideoProgressIndicatorState() { + listener = () { + if (mounted) { + setState(() {}); + } + }; + } + + late VoidCallback listener; + + MiniController get controller => widget.controller; + + @override + void initState() { + super.initState(); + controller.addListener(listener); + } + + @override + void deactivate() { + controller.removeListener(listener); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + const Color playedColor = Color.fromRGBO(255, 0, 0, 0.7); + const Color bufferedColor = Color.fromRGBO(50, 50, 200, 0.2); + const Color backgroundColor = Color.fromRGBO(200, 200, 200, 0.5); + + Widget progressIndicator; + if (controller.value.isInitialized) { + final int duration = controller.value.duration.inMilliseconds; + final int position = controller.value.position.inMilliseconds; + + int maxBuffering = 0; + for (final DurationRange range in controller.value.buffered) { + final int end = range.end.inMilliseconds; + if (end > maxBuffering) { + maxBuffering = end; + } + } + + progressIndicator = Stack( + fit: StackFit.passthrough, + children: [ + LinearProgressIndicator( + value: maxBuffering / duration, + valueColor: const AlwaysStoppedAnimation(bufferedColor), + backgroundColor: backgroundColor, + ), + LinearProgressIndicator( + value: position / duration, + valueColor: const AlwaysStoppedAnimation(playedColor), + backgroundColor: Colors.transparent, + ), + ], + ); + } else { + progressIndicator = const LinearProgressIndicator( + valueColor: AlwaysStoppedAnimation(playedColor), + backgroundColor: backgroundColor, + ); + } + return _VideoScrubber( + controller: controller, + child: Padding( + padding: const EdgeInsets.only(top: 5.0), + child: progressIndicator, + ), + ); + } +} diff --git a/ohos/automatic_video_player_test/pubspec.yaml b/ohos/automatic_video_player_test/pubspec.yaml new file mode 100644 index 00000000..1dec0630 --- /dev/null +++ b/ohos/automatic_video_player_test/pubspec.yaml @@ -0,0 +1,98 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: example +description: A new Flutter project. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ">=2.19.0 <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + video_player: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/video_player/video_player" + video_player_platform_interface: ">=6.1.0 <7.0.0" + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + path_provider: ^2.0.6 + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + + integration_test: + sdk: flutter + +dependency_overrides: + video_player_ohos: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/video_player/video_player_ohos" + path_provider: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/path_provider/path_provider" +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + assets: + - assets/flutter-mark-square-64.png + - assets/Butterfly-209.mp4 + - assets/video1.mp4 + - assets/Audio.mp3 diff --git a/ohos/automatic_video_player_test/result.json b/ohos/automatic_video_player_test/result.json new file mode 100644 index 00000000..e7ee8538 --- /dev/null +++ b/ohos/automatic_video_player_test/result.json @@ -0,0 +1,171 @@ +{"protocolVersion":"0.1.1","runnerVersion":null,"pid":237368,"type":"start","time":0} +{"suite":{"id":0,"platform":"vm","path":"E:\\Flutter\\flutter_packages\\packages\\video_player\\video_player_ohos\\example\\integration_test\\video_player_test.dart"},"type":"suite","time":0} +{"test":{"id":1,"name":"loading E:\\Flutter\\flutter_packages\\packages\\video_player\\video_player_ohos\\example\\integration_test\\video_player_test.dart","suiteID":0,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":1} +{"count":1,"time":6,"type":"allSuites"} +start hap build... +Running Hvigor task assembleHap... 64.0s +√ Built ohos\entry\build\default\outputs\default\entry-default-signed.hap. +installing hap. bundleName: com.example.example +{"testID":1,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":73639} +{"group":{"id":2,"suiteID":0,"parentID":null,"name":"","metadata":{"skip":false,"skipReason":null},"testCount":8,"line":null,"column":null,"url":null},"type":"group","time":73642} +{"group":{"id":3,"suiteID":0,"parentID":2,"name":"asset videos","metadata":{"skip":false,"skipReason":null},"testCount":5,"line":45,"column":3,"url":"e:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart"},"type":"group","time":73643} +{"test":{"id":4,"name":"asset videos registers expected implementation","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":73643} +{"testID":4,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":73965} +{"test":{"id":5,"name":"asset videos can be initialized","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":73965} +{"testID":5,"messageType":"print","message":"dev.flutter.pigeon.OhosVideoPlayerApi.initialize->","type":"print","time":73984} +{"testID":5,"messageType":"print","message":"[Instance of 'TextureMessage']","type":"print","time":73985} +{"testID":5,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":74241} +{"test":{"id":6,"name":"asset videos can be played","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":74241} +{"testID":6,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":75584} +{"test":{"id":7,"name":"asset videos can seek","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":75584} +{"testID":7,"messageType":"print","message":"══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════","type":"print","time":75922} +{"testID":7,"messageType":"print","message":"The following TestFailure was thrown running a test:","type":"print","time":75922} +{"testID":7,"messageType":"print","message":"Expected: Duration:<0:00:03.000000>","type":"print","time":75922} +{"testID":7,"messageType":"print","message":" Actual: Duration:<0:00:00.000000>","type":"print","time":75923} +{"testID":7,"messageType":"print","message":"","type":"print","time":75923} +{"testID":7,"messageType":"print","message":"When the exception was thrown, this was the stack:","type":"print","time":75925} +{"testID":7,"messageType":"print","message":"#4 main.. (file:///E:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart:79:7)","type":"print","time":75925} +{"testID":7,"messageType":"print","message":"","type":"print","time":75925} +{"testID":7,"messageType":"print","message":"","type":"print","time":75925} +{"testID":7,"messageType":"print","message":"(elided one frame from package:stack_trace)","type":"print","time":75925} +{"testID":7,"messageType":"print","message":"","type":"print","time":75927} +{"testID":7,"messageType":"print","message":"This was caught by the test expectation on the following line:","type":"print","time":75928} +{"testID":7,"messageType":"print","message":" file:///E:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart line 79","type":"print","time":75929} +{"testID":7,"messageType":"print","message":"The test description was:","type":"print","time":75929} +{"testID":7,"messageType":"print","message":" can seek","type":"print","time":75929} +{"testID":7,"messageType":"print","message":"════════════════════════════════════════════════════════════════════════════════════════════════════","type":"print","time":75929} +{"testID":7,"error":"Test failed. See exception logs above.\nThe test description was: can seek","stackTrace":"","isFailure":false,"type":"error","time":75933} +{"testID":7,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":75934} +{"test":{"id":8,"name":"asset videos can be paused","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":75934} +{"testID":8,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":79304} +{"group":{"id":9,"suiteID":0,"parentID":2,"name":"file-based videos","metadata":{"skip":false,"skipReason":null},"testCount":1,"line":98,"column":3,"url":"e:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart"},"type":"group","time":79304} +{"test":{"id":10,"name":"file-based videos test video player using static file() method as constructor","suiteID":0,"groupIDs":[2,9],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":79305} +{"testID":10,"error":"MissingPluginException(No implementation found for method getTemporaryDirectory on channel plugins.flutter.io/path_provider)","stackTrace":"package:flutter/src/services/platform_channel.dart 313:7 MethodChannel._invokeMethod\n===== asynchronous gap ===========================\npackage:stream_channel _GuaranteeSink.add\nc:/Users/XWX115~1/AppData/Local/Temp/flutter_tools.fae0dedb/flutter_test_listener.895bc456/listener.dart 53:22 main.\n","isFailure":false,"type":"error","time":79366} +{"testID":10,"error":"A MiniController was used after being disposed.\nOnce you have called dispose() on a MiniController, it can no longer be used.","stackTrace":"package:flutter/src/foundation/change_notifier.dart 157:9 ChangeNotifier.debugAssertNotDisposed.\npackage:flutter/src/foundation/change_notifier.dart 164:6 ChangeNotifier.debugAssertNotDisposed\npackage:flutter/src/foundation/change_notifier.dart 323:27 ChangeNotifier.dispose\npackage:example/mini_controller.dart 309:11 MiniController.dispose\n===== asynchronous gap ===========================\ndart:async _CustomZone.registerUnaryCallback\npackage:example/mini_controller.dart 304:7 MiniController.dispose\ne:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart 43:35 main.\n===== asynchronous gap ===========================\npackage:stream_channel _GuaranteeSink.add\nc:/Users/XWX115~1/AppData/Local/Temp/flutter_tools.fae0dedb/flutter_test_listener.895bc456/listener.dart 53:22 main.\n","isFailure":false,"type":"error","time":79380} +{"testID":10,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":79381} +{"group":{"id":11,"suiteID":0,"parentID":2,"name":"network videos","metadata":{"skip":false,"skipReason":null},"testCount":2,"line":123,"column":3,"url":"e:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart"},"type":"group","time":79381} +{"test":{"id":12,"name":"network videos reports buffering status","suiteID":0,"groupIDs":[2,11],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":79381} +{"testID":12,"messageType":"print","message":"══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════","type":"print","time":79623} +{"testID":12,"messageType":"print","message":"The following PlatformException was thrown running a test:","type":"print","time":79623} +{"testID":12,"messageType":"print","message":"PlatformException(videoError, video player had error!, Cannot get SourceMap info, dump raw stack:","type":"print","time":79624} +{"testID":12,"messageType":"print","message":"=====================Backtrace========================","type":"print","time":79624} +{"testID":12,"messageType":"print","message":"#00 pc 0000000000601964 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79624} +{"testID":12,"messageType":"print","message":"#01 pc 0000000000601e80 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79624} +{"testID":12,"messageType":"print","message":"#02 pc 00000000002af1ac /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79625} +{"testID":12,"messageType":"print","message":"#03 pc 0000000000166860 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79625} +{"testID":12,"messageType":"print","message":"#04 pc 00000000001663fc /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79625} +{"testID":12,"messageType":"print","message":"#05 pc 00000000001e9aac /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79626} +{"testID":12,"messageType":"print","message":"#06 pc 000000000050a624 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79626} +{"testID":12,"messageType":"print","message":"#07 pc 00000000004e2e8c /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":79626} +{"testID":12,"messageType":"print","message":"#08 pc 0000000000051274 /system/lib64/platformsdk/libace_napi.z.so","type":"print","time":79627} +{"testID":12,"messageType":"print","message":"#09 pc 0000000000017af0 /system/lib64/platformsdk/libmedia_soundpool.z.so","type":"print","time":79627} +{"testID":12,"messageType":"print","message":"#10 pc 000000000003f460 /system/lib64/platformsdk/libmedia_avplayer.z.so","type":"print","time":79628} +{"testID":12,"messageType":"print","message":"#11 pc 00000000000343d8 /system/lib64/platformsdk/libmedia_avplayer.z.so","type":"print","time":79628} +{"testID":12,"messageType":"print","message":"#12 pc 0000000000078d34 /system/lib64/platformsdk/libruntime.z.so","type":"print","time":79630} +{"testID":12,"messageType":"print","message":"#13 pc 000000000001ae7c /system/lib64/chipset-pub-sdk/libeventhandler.z.so, null)","type":"print","time":79630} +{"testID":12,"messageType":"print","message":"","type":"print","time":79636} +{"testID":12,"messageType":"print","message":"When the exception was thrown, this was the stack:","type":"print","time":79637} +{"testID":12,"messageType":"print","message":"#2 MiniController.initialize.errorListener (package:example/mini_controller.dart:291:31)","type":"print","time":79637} +{"testID":12,"messageType":"print","message":"#47 MiniController.initialize (package:example/mini_controller.dart:297:10)","type":"print","time":79638} +{"testID":12,"messageType":"print","message":"#49 MiniController.initialize (package:example/mini_controller.dart:248:19)","type":"print","time":79638} +{"testID":12,"messageType":"print","message":"#50 main.. (file:///E:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart:130:24)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#51 testWidgets.. (package:flutter_test/src/widget_tester.dart:171:29)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#53 testWidgets.. (package:flutter_test/src/widget_tester.dart:170:25)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#54 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:935:19)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#56 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:924:5)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#59 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:913:14)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#60 LiveTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1892:12)","type":"print","time":79639} +{"testID":12,"messageType":"print","message":"#61 IntegrationTestWidgetsFlutterBinding.runTest (package:integration_test/integration_test.dart:238:17)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#62 testWidgets. (package:flutter_test/src/widget_tester.dart:164:24)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#63 Declarer.test.. (package:test_api/src/backend/declarer.dart:215:19)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#65 Declarer.test.. (package:test_api/src/backend/declarer.dart:214:9)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#70 Declarer.test. (package:test_api/src/backend/declarer.dart:213:13)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#71 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:257:15)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#76 Invoker._waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:254:5)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#77 Invoker._onRun... (package:test_api/src/backend/invoker.dart:391:17)","type":"print","time":79640} +{"testID":12,"messageType":"print","message":"#79 Invoker._onRun... (package:test_api/src/backend/invoker.dart:389:11)","type":"print","time":79641} +{"testID":12,"messageType":"print","message":"#84 Invoker._onRun.. (package:test_api/src/backend/invoker.dart:380:9)","type":"print","time":79641} +{"testID":12,"messageType":"print","message":"#85 Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:423:15)","type":"print","time":79641} +{"testID":12,"messageType":"print","message":"#86 Invoker._onRun. (package:test_api/src/backend/invoker.dart:379:7)","type":"print","time":79641} +{"testID":12,"messageType":"print","message":"#93 Invoker._onRun (package:test_api/src/backend/invoker.dart:378:11)","type":"print","time":79641} +{"testID":12,"messageType":"print","message":"#94 LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11)","type":"print","time":79642} +{"testID":12,"messageType":"print","message":"#95 RemoteListener._runLiveTest. (package:test_api/src/backend/remote_listener.dart:273:16)","type":"print","time":79642} +{"testID":12,"messageType":"print","message":"#100 RemoteListener._runLiveTest (package:test_api/src/backend/remote_listener.dart:272:5)","type":"print","time":79642} +{"testID":12,"messageType":"print","message":"#101 RemoteListener._serializeTest. (package:test_api/src/backend/remote_listener.dart:220:7)","type":"print","time":79642} +{"testID":12,"messageType":"print","message":"#119 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12)","type":"print","time":79642} +{"testID":12,"messageType":"print","message":"#120 new _MultiChannel. (package:stream_channel/src/multi_channel.dart:159:31)","type":"print","time":79642} +{"testID":12,"messageType":"print","message":"#122 CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11)","type":"print","time":79643} +{"testID":12,"messageType":"print","message":"#136 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12)","type":"print","time":79643} +{"testID":12,"messageType":"print","message":"#137 main. (file:///C:/Users/XWX115~1/AppData/Local/Temp/flutter_tools.fae0dedb/flutter_test_listener.895bc456/listener.dart:53:22)","type":"print","time":79643} +{"testID":12,"messageType":"print","message":"#138 _runExtension (dart:developer-patch/developer.dart:91:23)","type":"print","time":79644} +{"testID":12,"messageType":"print","message":"(elided 105 frames from dart:async and package:stack_trace)","type":"print","time":79644} +{"testID":12,"messageType":"print","message":"","type":"print","time":79644} +{"testID":12,"messageType":"print","message":"The test description was:","type":"print","time":79644} +{"testID":12,"messageType":"print","message":" reports buffering status","type":"print","time":79645} +{"testID":12,"messageType":"print","message":"════════════════════════════════════════════════════════════════════════════════════════════════════","type":"print","time":79645} +{"testID":12,"error":"Test failed. See exception logs above.\nThe test description was: reports buffering status","stackTrace":"","isFailure":false,"type":"error","time":79646} +{"testID":12,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":79646} +{"test":{"id":13,"name":"network videos live stream duration != 0","suiteID":0,"groupIDs":[2,11],"metadata":{"skip":false,"skipReason":null},"line":154,"column":5,"url":"package:flutter_test/src/widget_tester.dart"},"type":"testStart","time":79646} +{"testID":13,"messageType":"print","message":"══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════","type":"print","time":101213} +{"testID":13,"messageType":"print","message":"The following PlatformException was thrown running a test:","type":"print","time":101214} +{"testID":13,"messageType":"print","message":"PlatformException(videoError, video player had error!, Cannot get SourceMap info, dump raw stack:","type":"print","time":101214} +{"testID":13,"messageType":"print","message":"=====================Backtrace========================","type":"print","time":101214} +{"testID":13,"messageType":"print","message":"#00 pc 0000000000601964 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101214} +{"testID":13,"messageType":"print","message":"#01 pc 0000000000601e80 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101214} +{"testID":13,"messageType":"print","message":"#02 pc 00000000002af1ac /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101215} +{"testID":13,"messageType":"print","message":"#03 pc 0000000000166860 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101215} +{"testID":13,"messageType":"print","message":"#04 pc 00000000001663fc /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101215} +{"testID":13,"messageType":"print","message":"#05 pc 00000000001e9aac /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101216} +{"testID":13,"messageType":"print","message":"#06 pc 000000000050a624 /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101216} +{"testID":13,"messageType":"print","message":"#07 pc 00000000004e2e8c /system/lib64/platformsdk/libark_jsruntime.so","type":"print","time":101216} +{"testID":13,"messageType":"print","message":"#08 pc 0000000000051274 /system/lib64/platformsdk/libace_napi.z.so","type":"print","time":101216} +{"testID":13,"messageType":"print","message":"#09 pc 0000000000017af0 /system/lib64/platformsdk/libmedia_soundpool.z.so","type":"print","time":101218} +{"testID":13,"messageType":"print","message":"#10 pc 000000000003f460 /system/lib64/platformsdk/libmedia_avplayer.z.so","type":"print","time":101218} +{"testID":13,"messageType":"print","message":"#11 pc 00000000000343d8 /system/lib64/platformsdk/libmedia_avplayer.z.so","type":"print","time":101218} +{"testID":13,"messageType":"print","message":"#12 pc 0000000000078d34 /system/lib64/platformsdk/libruntime.z.so","type":"print","time":101218} +{"testID":13,"messageType":"print","message":"#13 pc 000000000001ae7c /system/lib64/chipset-pub-sdk/libeventhandler.z.so, null)","type":"print","time":101219} +{"testID":13,"messageType":"print","message":"","type":"print","time":101219} +{"testID":13,"messageType":"print","message":"When the exception was thrown, this was the stack:","type":"print","time":101220} +{"testID":13,"messageType":"print","message":"#2 MiniController.initialize.errorListener (package:example/mini_controller.dart:291:31)","type":"print","time":101220} +{"testID":13,"messageType":"print","message":"#47 MiniController.initialize (package:example/mini_controller.dart:297:10)","type":"print","time":101220} +{"testID":13,"messageType":"print","message":"#49 MiniController.initialize (package:example/mini_controller.dart:248:19)","type":"print","time":101220} +{"testID":13,"messageType":"print","message":"#50 main.. (file:///E:/Flutter/flutter_packages/packages/video_player/video_player_ohos/example/integration_test/video_player_test.dart:160:34)","type":"print","time":101220} +{"testID":13,"messageType":"print","message":"#51 testWidgets.. (package:flutter_test/src/widget_tester.dart:171:29)","type":"print","time":101221} +{"testID":13,"messageType":"print","message":"#53 testWidgets.. (package:flutter_test/src/widget_tester.dart:170:25)","type":"print","time":101221} +{"testID":13,"messageType":"print","message":"#54 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:935:19)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#56 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:924:5)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#59 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:913:14)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#60 LiveTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1892:12)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#61 IntegrationTestWidgetsFlutterBinding.runTest (package:integration_test/integration_test.dart:238:17)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#62 testWidgets. (package:flutter_test/src/widget_tester.dart:164:24)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#63 Declarer.test.. (package:test_api/src/backend/declarer.dart:215:19)","type":"print","time":101222} +{"testID":13,"messageType":"print","message":"#65 Declarer.test.. (package:test_api/src/backend/declarer.dart:214:9)","type":"print","time":101223} +{"testID":13,"messageType":"print","message":"#70 Declarer.test. (package:test_api/src/backend/declarer.dart:213:13)","type":"print","time":101223} +{"testID":13,"messageType":"print","message":"#71 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:257:15)","type":"print","time":101223} +{"testID":13,"messageType":"print","message":"#76 Invoker._waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:254:5)","type":"print","time":101223} +{"testID":13,"messageType":"print","message":"#77 Invoker._onRun... (package:test_api/src/backend/invoker.dart:391:17)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#79 Invoker._onRun... (package:test_api/src/backend/invoker.dart:389:11)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#84 Invoker._onRun.. (package:test_api/src/backend/invoker.dart:380:9)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#85 Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:423:15)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#86 Invoker._onRun. (package:test_api/src/backend/invoker.dart:379:7)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#93 Invoker._onRun (package:test_api/src/backend/invoker.dart:378:11)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#94 LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11)","type":"print","time":101224} +{"testID":13,"messageType":"print","message":"#95 RemoteListener._runLiveTest. (package:test_api/src/backend/remote_listener.dart:273:16)","type":"print","time":101225} +{"testID":13,"messageType":"print","message":"#100 RemoteListener._runLiveTest (package:test_api/src/backend/remote_listener.dart:272:5)","type":"print","time":101225} +{"testID":13,"messageType":"print","message":"#101 RemoteListener._serializeTest. (package:test_api/src/backend/remote_listener.dart:220:7)","type":"print","time":101225} +{"testID":13,"messageType":"print","message":"#119 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12)","type":"print","time":101225} +{"testID":13,"messageType":"print","message":"#120 new _MultiChannel. (package:stream_channel/src/multi_channel.dart:159:31)","type":"print","time":101225} +{"testID":13,"messageType":"print","message":"#122 CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11)","type":"print","time":101226} +{"testID":13,"messageType":"print","message":"#136 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12)","type":"print","time":101226} +{"testID":13,"messageType":"print","message":"#137 main. (file:///C:/Users/XWX115~1/AppData/Local/Temp/flutter_tools.fae0dedb/flutter_test_listener.895bc456/listener.dart:53:22)","type":"print","time":101227} +{"testID":13,"messageType":"print","message":"#138 _runExtension (dart:developer-patch/developer.dart:91:23)","type":"print","time":101227} +{"testID":13,"messageType":"print","message":"(elided 105 frames from dart:async and package:stack_trace)","type":"print","time":101227} +{"testID":13,"messageType":"print","message":"","type":"print","time":101228} +{"testID":13,"messageType":"print","message":"The test description was:","type":"print","time":101228} +{"testID":13,"messageType":"print","message":" live stream duration != 0","type":"print","time":101228} +{"testID":13,"messageType":"print","message":"════════════════════════════════════════════════════════════════════════════════════════════════════","type":"print","time":101229} +{"testID":13,"error":"Test failed. See exception logs above.\nThe test description was: live stream duration != 0","stackTrace":"","isFailure":false,"type":"error","time":101229} +{"testID":13,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":101230} +{"test":{"id":14,"name":"(tearDownAll)","suiteID":0,"groupIDs":[2],"metadata":{"skip":false,"skipReason":null},"line":42,"column":5,"url":"package:integration_test/integration_test.dart"},"type":"testStart","time":101230} +{"testID":14,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":101238} +{"success":false,"type":"done","time":101958} diff --git a/ohos/automatic_video_player_test/test/widget_test.dart b/ohos/automatic_video_player_test/test/widget_test.dart new file mode 100644 index 00000000..71e65778 --- /dev/null +++ b/ohos/automatic_video_player_test/test/widget_test.dart @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/ohos/automatic_video_player_test/test_driver/app.dart b/ohos/automatic_video_player_test/test_driver/app.dart new file mode 100644 index 00000000..d44632dd --- /dev/null +++ b/ohos/automatic_video_player_test/test_driver/app.dart @@ -0,0 +1,13 @@ +import 'dart:ui'; + +import 'package:flutter_driver/driver_extension.dart'; +import '../integration_test/animation/animation_controller_test.dart' as app; +import '../integration_test/animation/animation_from_listener_test.dart' as app1; +import '../integration_test/animation/animations_test.dart' as app2; + + +void main() { + app.main(); + app1.main(); + app2.main(); +} \ No newline at end of file diff --git a/ohos/automatic_video_player_test/test_driver/app_test.dart b/ohos/automatic_video_player_test/test_driver/app_test.dart new file mode 100644 index 00000000..e69de29b diff --git a/ohos/automatic_video_player_test/test_driver/integration_test.dart b/ohos/automatic_video_player_test/test_driver/integration_test.dart new file mode 100644 index 00000000..6854dea6 --- /dev/null +++ b/ohos/automatic_video_player_test/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); \ No newline at end of file -- Gitee From 37ac4ff26c882a19d8fce6fd040017d9ce27618b Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:30:46 +0800 Subject: [PATCH 13/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4webview=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../ohos/.gitignore | 13 +++++ .../ohos/AppScope/app.json5 | 10 ++++ .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../ohos/build-profile.json5 | 41 ++++++++++++++ .../ohos/entry/.gitignore | 9 ++++ .../ohos/entry/build-profile.json5 | 29 ++++++++++ .../ohos/entry/hvigorfile.ts | 17 ++++++ .../main/ets/entryability/EntryAbility.ets | 27 ++++++++++ .../ohos/entry/src/main/ets/pages/Index.ets | 38 +++++++++++++ .../ets/plugins/GeneratedPluginRegistrant.ets | 30 +++++++++++ .../main/resources/base/element/color.json | 8 +++ .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../src/ohosTest/ets/test/Ability.test.ets | 50 ++++++++++++++++++ .../ohosTest/ets/testability/pages/Index.ets | 49 +++++++++++++++++ .../resources/base/element/color.json | 8 +++ .../ohosTest/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../ohos/hvigor/hvigor-config.json5 | 23 ++++++++ .../ohos/hvigorfile.ts | 17 ++++++ 18 files changed, 369 insertions(+) create mode 100644 ohos/automatic_webview_flutter_test/ohos/.gitignore create mode 100644 ohos/automatic_webview_flutter_test/ohos/AppScope/app.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 ohos/automatic_webview_flutter_test/ohos/build-profile.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/.gitignore create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/build-profile.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/hvigorfile.ts create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/ets/pages/Index.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/element/color.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/media/icon.png create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/media/icon.png create mode 100644 ohos/automatic_webview_flutter_test/ohos/hvigor/hvigor-config.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/hvigorfile.ts diff --git a/ohos/automatic_webview_flutter_test/ohos/.gitignore b/ohos/automatic_webview_flutter_test/ohos/.gitignore new file mode 100644 index 00000000..556a6a5d --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/.gitignore @@ -0,0 +1,13 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +oh-package-lock.json5 +*.har \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/AppScope/app.json5 b/ohos/automatic_webview_flutter_test/ohos/AppScope/app.json5 new file mode 100644 index 00000000..82a86c51 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.webview_flutter_demo", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/media/app_icon.png b/ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/color.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 00000000..3c712962 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/media/icon.png b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 Date: Wed, 13 Nov 2024 19:31:18 +0800 Subject: [PATCH 14/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4webview=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../resources/base/element/string.json | 8 +++ .../ohos/entry/oh-package.json5 | 15 +++++ .../ohos/entry/src/main/module.json5 | 62 ++++++++++++++++++ .../main/resources/base/element/string.json | 16 +++++ .../resources/base/profile/main_pages.json | 5 ++ .../main/resources/en_US/element/string.json | 16 +++++ .../main/resources/zh_CN/element/string.json | 16 +++++ .../entry/src/ohosTest/ets/test/List.test.ets | 20 ++++++ .../ohosTest/ets/testability/TestAbility.ets | 63 ++++++++++++++++++ .../ets/testrunner/OpenHarmonyTestRunner.ts | 64 +++++++++++++++++++ .../ohos/entry/src/ohosTest/module.json5 | 51 +++++++++++++++ .../resources/base/element/string.json | 16 +++++ .../resources/base/profile/test_pages.json | 5 ++ .../ohos/oh-package.json5 | 20 ++++++ 14 files changed, 377 insertions(+) create mode 100644 ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/element/string.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/oh-package.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/module.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/element/string.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/profile/main_pages.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/en_US/element/string.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/zh_CN/element/string.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/module.json5 create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/string.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json create mode 100644 ohos/automatic_webview_flutter_test/ohos/oh-package.json5 diff --git a/ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/element/string.json b/ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 00000000..7f69aa3f --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "webview_flutter_demo" + } + ] +} diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/oh-package.json5 b/ohos/automatic_webview_flutter_test/ohos/entry/oh-package.json5 new file mode 100644 index 00000000..677e8c6e --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/oh-package.json5 @@ -0,0 +1,15 @@ +{ + "license": "", + "devDependencies": {}, + "author": "", + "name": "entry", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": { + "@ohos/flutter_ohos": "file:../har/flutter.har", + "path_provider_ohos": "file:../har/path_provider_ohos.har", + "webview_flutter_ohos": "file:../har/webview_flutter_ohos.har", + "integration_test": "file:../har/integration_test.har" + } +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/main/module.json5 b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/module.json5 new file mode 100644 index 00000000..1aeeb2da --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/module.json5 @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + ] + } +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/element/string.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000..f9459551 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/profile/main_pages.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 00000000..1898d94f --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/en_US/element/string.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 00000000..f9459551 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/zh_CN/element/string.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 00000000..d2efde92 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "webview_flutter" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/test/List.test.ets b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 00000000..f4140030 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 00000000..4ca645e6 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; + +export default class TestAbility extends UIAbility { + onCreate(want, launchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 00000000..1def08f2 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/module.json5 b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 00000000..fab77ce2 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/string.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 00000000..65d8fa5a --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 00000000..b7e7343c --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/ohos/automatic_webview_flutter_test/ohos/oh-package.json5 b/ohos/automatic_webview_flutter_test/ohos/oh-package.json5 new file mode 100644 index 00000000..3f4630af --- /dev/null +++ b/ohos/automatic_webview_flutter_test/ohos/oh-package.json5 @@ -0,0 +1,20 @@ +{ + "modelVersion": "5.0.0", + "license": "", + "devDependencies": { + "@ohos/hypium": "1.0.6" + }, + "author": "", + "name": "apptemplate", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": {}, + "overrides": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "path_provider_ohos": "file:./har/path_provider_ohos.har", + "webview_flutter_ohos": "file:./har/webview_flutter_ohos.har", + "@ohos/flutter_module": "file:./entry", + "integration_test": "file:./har/integration_test.har" + } +} \ No newline at end of file -- Gitee From ba8034ec025432108e8a795986d76fe78aa8358a Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:32:25 +0800 Subject: [PATCH 15/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4webview=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../assets/www/styles/style.css | 3 + .../webview_flutter_test.dart | 1310 +++++++++++++++++ .../lib/legacy/navigation_decision.dart | 12 + .../lib/legacy/navigation_request.dart | 19 + .../lib/legacy/web_view.dart | 696 +++++++++ .../lib/main.dart | 809 ++++++++++ .../pubspec.yaml | 65 + 7 files changed, 2914 insertions(+) create mode 100644 ohos/automatic_webview_flutter_test/assets/www/styles/style.css create mode 100644 ohos/automatic_webview_flutter_test/integration_test/webview_flutter_test.dart create mode 100644 ohos/automatic_webview_flutter_test/lib/legacy/navigation_decision.dart create mode 100644 ohos/automatic_webview_flutter_test/lib/legacy/navigation_request.dart create mode 100644 ohos/automatic_webview_flutter_test/lib/legacy/web_view.dart create mode 100644 ohos/automatic_webview_flutter_test/lib/main.dart create mode 100644 ohos/automatic_webview_flutter_test/pubspec.yaml diff --git a/ohos/automatic_webview_flutter_test/assets/www/styles/style.css b/ohos/automatic_webview_flutter_test/assets/www/styles/style.css new file mode 100644 index 00000000..c2140b8b --- /dev/null +++ b/ohos/automatic_webview_flutter_test/assets/www/styles/style.css @@ -0,0 +1,3 @@ +h1 { + color: blue; +} \ No newline at end of file diff --git a/ohos/automatic_webview_flutter_test/integration_test/webview_flutter_test.dart b/ohos/automatic_webview_flutter_test/integration_test/webview_flutter_test.dart new file mode 100644 index 00000000..6e2e2cbf --- /dev/null +++ b/ohos/automatic_webview_flutter_test/integration_test/webview_flutter_test.dart @@ -0,0 +1,1310 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test is run using `flutter drive` by the CI (see /script/tool/README.md +// in this repository for details on driving that tooling manually), but can +// also be run using `flutter test` directly during development. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter_ohos/src/ohos_webview.dart' as ohos; +import 'package:webview_flutter_ohos/src/ohos_webview.g.dart'; +import 'package:webview_flutter_ohos/src/ohos_webview_api_impls.dart'; +import 'package:webview_flutter_ohos/src/instance_manager.dart'; +import 'package:webview_flutter_ohos/src/weak_reference_utils.dart'; +import 'package:webview_flutter_ohos/webview_flutter_ohos.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +void main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); + unawaited(server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else if (request.uri.path == '/secondary.txt') { + request.response.writeln('How are you today?'); + } else if (request.uri.path == '/headers') { + request.response.writeln('${request.headers}'); + } else if (request.uri.path == '/favicon.ico') { + request.response.statusCode = HttpStatus.notFound; + } else if (request.uri.path == '/http-basic-authentication') { + final bool isAuthenticating = request.headers['Authorization'] != null; + if (isAuthenticating) { + request.response.writeln('Authorized'); + } else { + request.response.headers + .add('WWW-Authenticate', 'Basic realm="Test realm"'); + request.response.statusCode = HttpStatus.unauthorized; + } + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + })); + final String prefixUrl = 'http://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; + final String basicAuthUrl = '$prefixUrl/http-basic-authentication'; + + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(primaryUrl)), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; + + final String? currentUrl = await controller.currentUrl(); + + print("====loadRequest-${(currentUrl==primaryUrl) == true ? "success" : "failed"}:"); + + expect(currentUrl, primaryUrl); + }); + + + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; + expectLater(controller.runJavaScriptReturningResult('1 + 1'), completion(2)); + int result = await controller.runJavaScriptReturningResult('1 + 1') as int; + print("====runJavaScriptReturningResult-" + "${result == 2 ? "success" : "failed"}:"); + }); + + testWidgets('loadRequest with headers', (WidgetTester tester) async { + final Map headers = { + 'test_header': 'flutter_test_header' + }; + + final StreamController pageLoads = StreamController(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((String url) => pageLoads.add(url))); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse(headersUrl), + headers: headers, + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = await controller.runJavaScriptReturningResult( + 'document.documentElement.innerText', + ) as String; + + print("====loadRequest with headers-" + "${(content.contains('flutter_test_header')) == true ? "success" : "failed"}:"); + + expect(content.contains('flutter_test_header'), isTrue); + }); + + testWidgets('JavascriptChannel', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + final Completer channelCompleter = Completer(); + await controller.addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Echo', + onMessageReceived: (JavaScriptMessage message) { + channelCompleter.complete(message.message); + }, + ), + ); + + await controller.loadHtmlString( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; + + await controller.runJavaScript('Echo.postMessage("hello");'); + await expectLater(channelCompleter.future, completion('hello')); + final String result = await channelCompleter.future; + print("====JavascriptChannel - " + "${(result == 'hello') == true ? "success" : "failed"}:"); + }); + + testWidgets('resize webview', (WidgetTester tester) async { + final Completer initialResizeCompleter = Completer(); + final Completer buttonTapResizeCompleter = Completer(); + final Completer onPageFinished = Completer(); + + bool resizeButtonTapped = false; + await tester.pumpWidget(ResizableWebView( + onResize: () { + if (resizeButtonTapped) { + buttonTapResizeCompleter.complete(); + } else { + initialResizeCompleter.complete(); + } + }, + onPageFinished: () => onPageFinished.complete(), + )); + + await onPageFinished.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + + resizeButtonTapped = true; + + await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.pumpAndSettle(); + + await expectLater(buttonTapResizeCompleter.future, completes); + try { + await buttonTapResizeCompleter.future; + print("====resize webview - success :"); + } catch (e) { + print("====resize webview - failed:"); + } + + }); + + testWidgets('set custom userAgent', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited(controller.setUserAgent('Custom_User_Agent1')); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageFinished.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller + .loadRequest(LoadRequestParams(uri: Uri.parse('about:blank'))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; + + final String? customUserAgent = await controller.getUserAgent(); + + print("====set custom userAgent-" + "${(customUserAgent == 'Custom_User_Agent1') == true ? "success" : "failed"}:"); + + // expect(customUserAgent, 'Custom_User_Agent1'); + }); + + group('Video playback policy', () { + late String videoTestBase64; + setUpAll(() async { + final ByteData videoData = + await rootBundle.load('assets/sample_video.mp4'); + final String base64VideoData = + base64Encode(Uint8List.view(videoData.buffer)); + final String videoTest = ''' + + Video auto play + + + + +
+
+ +
+ + + '''; + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); + }); + + testWidgets('Video playback policy - Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + OhosWebViewController controller = OhosWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + OhosNavigationDelegate delegate = OhosNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = OhosWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + delegate = OhosNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + + print("====Video playback policy - Auto media playback" + "${!isPaused ? "success" : "failed"}:"); + }); + + testWidgets('Video plays inline', (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + final OhosWebViewController controller = OhosWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + final OhosNavigationDelegate delegate = OhosNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + unawaited(controller.addJavaScriptChannel( + JavaScriptChannelParams( + name: 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ), + )); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final bool fullScreen = await controller + .runJavaScriptReturningResult('isFullScreen();') as bool; + + print("====Video plays inline-" + "${fullScreen == false ? "success" : "failed"}:"); + + // expect(fullScreen, false); + }, skip: true); + }); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = + await rootBundle.load('assets/sample_audio.ogg'); + final String base64AudioData = + base64Encode(Uint8List.view(audioData.buffer)); + final String audioTest = ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + OhosWebViewController controller = OhosWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited(controller.setMediaPlaybackRequiresUserGesture(false)); + OhosNavigationDelegate delegate = OhosNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$audioTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + + var flag = isPaused == false; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = OhosWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + delegate = OhosNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$audioTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + print("====Auto media playback-" + "${!isPaused == true ? "success" : "failed"}:"); + expect(isPaused, false); + + }); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + const String getTitleTest = ''' + + Some title + + + + + '''; + final String getTitleTestBase64 = + base64Encode(const Utf8Encoder().convert(getTitleTest)); + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + // On at least iOS, it does not appear to be guaranteed that the native + // code has the title when the page load completes. Execute some JavaScript + // before checking the title to ensure that the page has been fully parsed + // and processed. + await controller.runJavaScript('1;'); + + final String? title = await controller.getTitle(); + + print("====getTitle-" + "${(title == 'Some title') == true ? "success" : "failed"}:"); + + // expect(title, 'Some title'); + }); + + group('Programmatic Scroll', () { + testWidgets('setAndGetAndListenScrollPosition', + (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = + base64Encode(const Utf8Encoder().convert(scrollTestPage)); + + final Completer pageLoaded = Completer(); + ScrollPositionChange? recordedPosition; + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + unawaited(controller.setOnScrollPositionChange( + (ScrollPositionChange contentOffsetChange) { + recordedPosition = contentOffsetChange; + })); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + Offset scrollPos = await controller.getScrollPosition(); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + // Get the initial position; this ensures that scrollTo is actually + // changing something, but also gives the native view's scroll position + // time to settle. + expect(scrollPos.dx, isNot(X_SCROLL)); + expect(scrollPos.dy, isNot(Y_SCROLL)); + expect(recordedPosition, null); + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + print("====setAndGetAndListenScrollPosition - 1 -" + "${scrollPos.dx == X_SCROLL ? "success" : "failed"}:"); + expect(scrollPos.dx, X_SCROLL); + print("====setAndGetAndListenScrollPosition - 2 -" + "${scrollPos.dy == Y_SCROLL ? "success" : "failed"}:"); + expect(scrollPos.dy, Y_SCROLL); + print("====setAndGetAndListenScrollPosition - 3 -" + "${recordedPosition?.x == X_SCROLL ? "success" : "failed"}:"); + expect(recordedPosition?.x, X_SCROLL); + print("====setAndGetAndListenScrollPosition - 4 -" + "${recordedPosition?.y == Y_SCROLL ? "success" : "failed"}:"); + expect(recordedPosition?.y, Y_SCROLL); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + print("====setAndGetAndListenScrollPosition - 5 -" + "${scrollPos.dx == X_SCROLL * 2 ? "success" : "failed"}:"); + expect(scrollPos.dx, X_SCROLL * 2); + print("====setAndGetAndListenScrollPosition - 6 -" + "${scrollPos.dy == Y_SCROLL * 2 ? "success" : "failed"}:"); + expect(scrollPos.dy, Y_SCROLL * 2); + print("====setAndGetAndListenScrollPosition - 7 -" + "${recordedPosition?.x == X_SCROLL * 2 ? "success" : "failed"}:"); + expect(recordedPosition?.x, X_SCROLL * 2); + print("====setAndGetAndListenScrollPosition - 8 -" + "${recordedPosition?.y == Y_SCROLL * 2 ? "success" : "failed"}:"); + expect(recordedPosition?.y, Y_SCROLL * 2); + + }); + }); + + group('NavigationDelegate', () { + const String blankPage = ''; + final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + '${base64Encode(const Utf8Encoder().convert(blankPage))}'; + + testWidgets('can allow requests', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited( + delegate.setOnNavigationRequest((NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }), + ); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(blankPageEncoded)), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for the next page load. + + final String? currentUrl = await controller.currentUrl(); + + print("====can allow requests-" + "${(currentUrl == secondaryUrl) == true ? "success" : "failed"}:"); + + // expect(currentUrl, secondaryUrl); + }); + + testWidgets('onWebResourceError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited( + delegate.setOnWebResourceError((WebResourceError error) { + errorCompleter.complete(error); + }), + ); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + final WebResourceError error = await errorCompleter.future; + expect(error, isNotNull); + + expect(error.errorType, isNotNull); + + print("====onWebResourceError-" + "${(error.url?.startsWith('https://www.notawebsite..com')) == true ? "success" : "failed"}:"); + + expect( + error.url?.startsWith('https://www.notawebsite..com'), + isTrue, + ); + }); + + testWidgets('onWebResourceError is not called with valid url', + (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited( + delegate.setOnPageFinished((_) => pageFinishCompleter.complete()), + ); + unawaited( + delegate.setOnWebResourceError((WebResourceError error) { + errorCompleter.complete(error); + }), + ); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + print("test juage"); + final result = errorCompleter.future; + print("====onWebResourceError is not called with valid url-" + "${(result == doesNotComplete) == true ? "success" : "failed"}:"); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('can block requests', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(delegate + .setOnNavigationRequest((NavigationRequest navigationRequest) { + return (navigationRequest.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + })); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + + await controller + .loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller + .runJavaScript('location.href = "https://www.youtube.com/"'); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoaded.future + .timeout(const Duration(milliseconds: 500), onTimeout: () => false); + final String? currentUrl = await controller.currentUrl(); + final result = currentUrl?.contains('youtube.com'); + print("====can block requests-" + "${(!result!) ? "success" : "failed"}:"); + + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(delegate + .setOnNavigationRequest((NavigationRequest navigationRequest) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate); + return decision; + })); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller + .loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for second page to load. + + final String? currentUrl = await controller.currentUrl(); + + print("====supports asynchronous decisions-" + "${(currentUrl == secondaryUrl) == true ? "success" : "failed"}:"); + + expect(currentUrl, secondaryUrl); + }); + + testWidgets('can receive url changes', (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller + .loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + await delegate.setOnPageFinished((_) {}); + + final Completer urlChangeCompleter = Completer(); + await delegate.setOnUrlChange((UrlChange change) { + urlChangeCompleter.complete(change.url); + }); + + await controller.runJavaScript('location.href = "$primaryUrl"'); + + final String? result = await urlChangeCompleter.future; + print("====can receive url changes-" + "${(result == primaryUrl) == true ? "success" : "failed"}:"); + await expectLater(urlChangeCompleter.future, completion(primaryUrl)); + }); + + testWidgets('can receive updates to history state', + (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller + .loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + await delegate.setOnPageFinished((_) {}); + + final Completer urlChangeCompleter = Completer(); + await delegate.setOnUrlChange((UrlChange change) { + urlChangeCompleter.complete(change.url); + }); + + await controller.runJavaScript( + 'window.history.pushState({}, "", "secondary.txt");', + ); + + final String? result = await urlChangeCompleter.future; + print("====can receive updates to history state-" + "${(result == secondaryUrl) == true ? "success" : "failed"}:"); + await expectLater(urlChangeCompleter.future, completion(secondaryUrl)); + }); + }); + + testWidgets( + 'JavaScript does not run in parent window', + (WidgetTester tester) async { + const String iframe = ''' + + + '''; + final String iframeTestBase64 = + base64Encode(const Utf8Encoder().convert(iframe)); + + final String openWindowTest = ''' + + + + XSS test + + + + + + '''; + final String openWindowTestBase64 = + base64Encode(const Utf8Encoder().convert(openWindowTest)); + + final Completer pageLoadCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited( + delegate.setOnPageFinished((_) => pageLoadCompleter.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoadCompleter.future; + + final bool iframeLoaded = + await controller.runJavaScriptReturningResult('iframeLoaded') as bool; + expect(iframeLoaded, true); + + final String elementText = await controller.runJavaScriptReturningResult( + 'document.querySelector("p") && document.querySelector("p").textContent', + ) as String; + + print("====JavaScript does not run in parent window-" + "${(elementText == 'null') == true ? "success" : "failed"}:"); + + // expect(elementText, 'null'); + }, + ); + + testWidgets( + '`AndroidWebViewController` can be reused with a new `AndroidWebViewWidget`', + (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete())); + unawaited(controller.setPlatformNavigationDelegate(delegate)); + await controller + .loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + pageLoaded = Completer(); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(primaryUrl)), + ); + + print("====`AndroidWebViewController` can be reused with a new `AndroidWebViewWidget` - " + "success"); + }, + ); + +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); + + final VoidCallback onResize; + final VoidCallback onPageFinished; + + @override + State createState() => ResizableWebViewState(); +} + +class ResizableWebViewState extends State { + late final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => widget.onPageFinished()), + ) + ..addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Resize', + onMessageReceived: (_) { + widget.onResize(); + }, + ), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', + ), + ), + ); + + double webViewWidth = 200; + double webViewHeight = 200; + + static const String resizePage = ''' + + Resize test + + + + + + '''; + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + SizedBox( + width: webViewWidth, + height: webViewHeight, + child: PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context), + ), + TextButton( + key: const Key('resizeButton'), + onPressed: () { + setState(() { + webViewWidth += 100.0; + webViewHeight += 100.0; + }); + }, + child: const Text('ResizeButton'), + ), + ], + ), + ); + } +} + +class CopyableObjectWithCallback with Copyable { + CopyableObjectWithCallback(this.callback); + + final VoidCallback callback; + + @override + CopyableObjectWithCallback copy() { + return CopyableObjectWithCallback(callback); + } +} + +class ClassWithCallbackClass { + ClassWithCallbackClass() { + callbackClass = CopyableObjectWithCallback( + withWeakReferenceTo( + this, + (WeakReference weakReference) { + return () { + // Weak reference to `this` in callback. + // ignore: unnecessary_statements + weakReference; + }; + }, + ), + ); + } + + late final CopyableObjectWithCallback callbackClass; +} diff --git a/ohos/automatic_webview_flutter_test/lib/legacy/navigation_decision.dart b/ohos/automatic_webview_flutter_test/lib/legacy/navigation_decision.dart new file mode 100644 index 00000000..d8178acd --- /dev/null +++ b/ohos/automatic_webview_flutter_test/lib/legacy/navigation_decision.dart @@ -0,0 +1,12 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A decision on how to handle a navigation request. +enum NavigationDecision { + /// Prevent the navigation from taking place. + prevent, + + /// Allow the navigation to take place. + navigate, +} diff --git a/ohos/automatic_webview_flutter_test/lib/legacy/navigation_request.dart b/ohos/automatic_webview_flutter_test/lib/legacy/navigation_request.dart new file mode 100644 index 00000000..6d33126b --- /dev/null +++ b/ohos/automatic_webview_flutter_test/lib/legacy/navigation_request.dart @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Information about a navigation action that is about to be executed. +class NavigationRequest { + NavigationRequest._({required this.url, required this.isForMainFrame}); + + /// The URL that will be loaded if the navigation is executed. + final String url; + + /// Whether the navigation request is to be loaded as the main frame. + final bool isForMainFrame; + + @override + String toString() { + return '$NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; + } +} diff --git a/ohos/automatic_webview_flutter_test/lib/legacy/web_view.dart b/ohos/automatic_webview_flutter_test/lib/legacy/web_view.dart new file mode 100644 index 00000000..4887023c --- /dev/null +++ b/ohos/automatic_webview_flutter_test/lib/legacy/web_view.dart @@ -0,0 +1,696 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_ohos/src/webview_flutter_ohos_legacy.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import 'navigation_decision.dart'; +import 'navigation_request.dart'; + +/// Optional callback invoked when a web view is first created. [controller] is +/// the [WebViewController] for the created web view. +typedef WebViewCreatedCallback = void Function(WebViewController controller); + +/// Decides how to handle a specific navigation request. +/// +/// The returned [NavigationDecision] determines how the navigation described by +/// `navigation` should be handled. +/// +/// See also: [WebView.navigationDelegate]. +typedef NavigationDelegate = FutureOr Function( + NavigationRequest navigation); + +/// Signature for when a [WebView] has started loading a page. +typedef PageStartedCallback = void Function(String url); + +/// Signature for when a [WebView] has finished loading a page. +typedef PageFinishedCallback = void Function(String url); + +/// Signature for when a [WebView] is loading a page. +typedef PageLoadingCallback = void Function(int progress); + +/// Signature for when a [WebView] has failed to load a resource. +typedef WebResourceErrorCallback = void Function(WebResourceError error); + +/// A web view widget for showing html content. +/// +/// The [WebView] widget wraps around the [AndroidWebView] or +/// [SurfaceAndroidWebView] classes and acts like a facade which makes it easier +/// to inject a [AndroidWebView] or [SurfaceAndroidWebView] control into the +/// widget tree. +/// +/// The [WebView] widget is controlled using the [WebViewController] which is +/// provided through the `onWebViewCreated` callback. +/// +/// In this example project it's main purpose is to facilitate integration +/// testing of the `webview_flutter_android` package. +class WebView extends StatefulWidget { + /// Creates a new web view. + /// + /// The web view can be controlled using a `WebViewController` that is passed to the + /// `onWebViewCreated` callback once the web view is created. + /// + /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. + const WebView({ + super.key, + this.onWebViewCreated, + this.initialUrl, + this.initialCookies = const [], + this.javascriptMode = JavascriptMode.disabled, + this.javascriptChannels, + this.navigationDelegate, + this.gestureRecognizers, + this.onPageStarted, + this.onPageFinished, + this.onProgress, + this.onWebResourceError, + this.debuggingEnabled = false, + this.gestureNavigationEnabled = false, + this.userAgent, + this.zoomEnabled = true, + this.initialMediaPlaybackPolicy = + AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.allowsInlineMediaPlayback = false, + this.backgroundColor, + }); + + /// The WebView platform that's used by this WebView. + /// + /// The default value is [AndroidWebView]. + static WebViewPlatform platform = OhosWebView(); + + /// If not null invoked once the web view is created. + final WebViewCreatedCallback? onWebViewCreated; + + /// Which gestures should be consumed by the web view. + /// + /// It is possible for other gesture recognizers to be competing with the web view on pointer + /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle + /// vertical drags. The web view will claim gestures that are recognized by any of the + /// recognizers on this list. + /// + /// When this set is empty or null, the web view will only handle pointer events for gestures that + /// were not claimed by any other gesture recognizer. + final Set>? gestureRecognizers; + + /// The initial URL to load. + final String? initialUrl; + + /// The initial cookies to set. + final List initialCookies; + + /// Whether JavaScript execution is enabled. + final JavascriptMode javascriptMode; + + /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. + /// + /// For each [JavascriptChannel] in the set, a channel object is made available for the + /// JavaScript code in a window property named [JavascriptChannel.name]. + /// The JavaScript code can then call `postMessage` on that object to send a message that will be + /// passed to [JavascriptChannel.onMessageReceived]. + /// + /// For example for the following [JavascriptChannel]: + /// + /// ```dart + /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); + /// ``` + /// + /// JavaScript code can call: + /// + /// ```javascript + /// Print.postMessage('Hello'); + /// ``` + /// + /// To asynchronously invoke the message handler which will print the message to standard output. + /// + /// Adding a new JavaScript channel only takes affect after the next page is loaded. + /// + /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple + /// channels in the list. + /// + /// A null value is equivalent to an empty set. + final Set? javascriptChannels; + + /// A delegate function that decides how to handle navigation actions. + /// + /// When a navigation is initiated by the WebView (e.g when a user clicks a link) + /// this delegate is called and has to decide how to proceed with the navigation. + /// + /// See [NavigationDecision] for possible decisions the delegate can take. + /// + /// When null all navigation actions are allowed. + /// + /// Caveats on Android: + /// + /// * Navigation actions targeted to the main frame can be intercepted, + /// navigation actions targeted to subframes are allowed regardless of the value + /// returned by this delegate. + /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were + /// triggered by a user gesture, this disables some of Chromium's security mechanisms. + /// A navigationDelegate should only be set when loading trusted content. + /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have + /// a later version): + /// * When a navigationDelegate is set pages with frames are not properly handled by the + /// webview, and frames will be opened in the main frame. + /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. + final NavigationDelegate? navigationDelegate; + + /// Controls whether inline playback of HTML5 videos is allowed on iOS. + /// + /// This field is ignored on Android because Android allows it by default. + /// + /// By default `allowsInlineMediaPlayback` is false. + final bool allowsInlineMediaPlayback; + + /// Invoked when a page starts loading. + final PageStartedCallback? onPageStarted; + + /// Invoked when a page has finished loading. + /// + /// This is invoked only for the main frame. + /// + /// When [onPageFinished] is invoked on Android, the page being rendered may + /// not be updated yet. + /// + /// When invoked on iOS or Android, any JavaScript code that is embedded + /// directly in the HTML has been loaded and code injected with + /// [WebViewController.evaluateJavascript] can assume this. + final PageFinishedCallback? onPageFinished; + + /// Invoked when a page is loading. + final PageLoadingCallback? onProgress; + + /// Invoked when a web resource has failed to load. + /// + /// This callback is only called for the main page. + final WebResourceErrorCallback? onWebResourceError; + + /// Controls whether WebView debugging is enabled. + /// + /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). + /// + /// WebView debugging is enabled by default in dev builds on iOS. + /// + /// To debug WebViews on iOS: + /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) + /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> + /// + /// By default `debuggingEnabled` is false. + final bool debuggingEnabled; + + /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. + /// + /// This only works on iOS. + /// + /// By default `gestureNavigationEnabled` is false. + final bool gestureNavigationEnabled; + + /// A Boolean value indicating whether the WebView should support zooming using its on-screen zoom controls and gestures. + /// + /// By default 'zoomEnabled' is true + final bool zoomEnabled; + + /// The value used for the HTTP User-Agent: request header. + /// + /// When null the platform's webview default is used for the User-Agent header. + /// + /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. + /// + /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. + /// + /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom + /// user agent. + /// + /// By default `userAgent` is null. + final String? userAgent; + + /// Which restrictions apply on automatic media playback. + /// + /// This initial value is applied to the platform's webview upon creation. Any following + /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). + /// + /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. + final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; + + /// The background color of the [WebView]. + /// + /// When `null` the platform's webview default background color is used. By + /// default [backgroundColor] is `null`. + final Color? backgroundColor; + + @override + State createState() => _WebViewState(); +} + +class _WebViewState extends State { + final Completer _controller = + Completer(); + late final JavascriptChannelRegistry _javascriptChannelRegistry; + late final _PlatformCallbacksHandler _platformCallbacksHandler; + + @override + void initState() { + super.initState(); + _platformCallbacksHandler = _PlatformCallbacksHandler(widget); + _javascriptChannelRegistry = + JavascriptChannelRegistry(widget.javascriptChannels); + } + + @override + void didUpdateWidget(WebView oldWidget) { + super.didUpdateWidget(oldWidget); + _controller.future.then((WebViewController controller) { + controller.updateWidget(widget); + }); + } + + @override + Widget build(BuildContext context) { + return WebView.platform.build( + context: context, + onWebViewPlatformCreated: + (WebViewPlatformController? webViewPlatformController) { + final WebViewController controller = WebViewController( + widget, + webViewPlatformController!, + _javascriptChannelRegistry, + ); + _controller.complete(controller); + + if (widget.onWebViewCreated != null) { + widget.onWebViewCreated!(controller); + } + }, + webViewPlatformCallbacksHandler: _platformCallbacksHandler, + creationParams: CreationParams( + initialUrl: widget.initialUrl, + webSettings: _webSettingsFromWidget(widget), + javascriptChannelNames: + _javascriptChannelRegistry.channels.keys.toSet(), + autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, + userAgent: widget.userAgent, + backgroundColor: widget.backgroundColor, + cookies: widget.initialCookies, + ), + javascriptChannelRegistry: _javascriptChannelRegistry, + ); + } +} + +class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { + _PlatformCallbacksHandler(this._webView); + + final WebView _webView; + + @override + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) async { + if (url.startsWith('https://www.youtube.com/')) { + debugPrint('blocking navigation to $url'); + return false; + } + debugPrint('allowing navigation to $url'); + return true; + } + + @override + void onPageStarted(String url) { + if (_webView.onPageStarted != null) { + _webView.onPageStarted!(url); + } + } + + @override + void onPageFinished(String url) { + if (_webView.onPageFinished != null) { + _webView.onPageFinished!(url); + } + } + + @override + void onProgress(int progress) { + if (_webView.onProgress != null) { + _webView.onProgress!(progress); + } + } + + @override + void onWebResourceError(WebResourceError error) { + if (_webView.onWebResourceError != null) { + _webView.onWebResourceError!(error); + } + } +} + +/// Controls a [WebView]. +/// +/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] +/// callback for a [WebView] widget. +class WebViewController { + /// Creates a [WebViewController] which can be used to control the provided + /// [WebView] widget. + WebViewController( + this._widget, + this._webViewPlatformController, + this._javascriptChannelRegistry, + ) { + _settings = _webSettingsFromWidget(_widget); + } + + final JavascriptChannelRegistry _javascriptChannelRegistry; + + final WebViewPlatformController _webViewPlatformController; + + late WebSettings _settings; + + WebView _widget; + + /// Loads the file located on the specified [absoluteFilePath]. + /// + /// The [absoluteFilePath] parameter should contain the absolute path to the + /// file as it is stored on the device. For example: + /// `/Users/username/Documents/www/index.html`. + /// + /// Throws an ArgumentError if the [absoluteFilePath] does not exist. + Future loadFile(String absoluteFilePath) { + return _webViewPlatformController.loadFile(absoluteFilePath); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + /// + /// Throws an ArgumentError if [key] is not part of the specified assets + /// in the pubspec.yaml file. + Future loadFlutterAsset(String key) { + return _webViewPlatformController.loadFlutterAsset(key); + } + + /// Loads the supplied HTML string. + /// + /// The [baseUrl] parameter is used when resolving relative URLs within the + /// HTML string. + Future loadHtmlString(String html, {String? baseUrl}) { + return _webViewPlatformController.loadHtmlString( + html, + baseUrl: baseUrl, + ); + } + + /// Loads the specified URL. + /// + /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will + /// be added as key value pairs of HTTP headers for the request. + /// + /// `url` must not be null. + /// + /// Throws an ArgumentError if `url` is not a valid URL string. + Future loadUrl( + String url, { + Map? headers, + }) async { + _validateUrlString(url); + return _webViewPlatformController.loadUrl(url, headers); + } + + /// Loads a page by making the specified request. + Future loadRequest(WebViewRequest request) async { + return _webViewPlatformController.loadRequest(request); + } + + /// Accessor to the current URL that the WebView is displaying. + /// + /// If [WebView.initialUrl] was never specified, returns `null`. + /// Note that this operation is asynchronous, and it is possible that the + /// current URL changes again by the time this function returns (in other + /// words, by the time this future completes, the WebView may be displaying a + /// different URL). + Future currentUrl() { + return _webViewPlatformController.currentUrl(); + } + + /// Checks whether there's a back history item. + /// + /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has + /// changed by the time the future completed. + Future canGoBack() { + return _webViewPlatformController.canGoBack(); + } + + /// Checks whether there's a forward history item. + /// + /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has + /// changed by the time the future completed. + Future canGoForward() { + return _webViewPlatformController.canGoForward(); + } + + /// Goes back in the history of this WebView. + /// + /// If there is no back history item this is a no-op. + Future goBack() { + return _webViewPlatformController.goBack(); + } + + /// Goes forward in the history of this WebView. + /// + /// If there is no forward history item this is a no-op. + Future goForward() { + return _webViewPlatformController.goForward(); + } + + /// Reloads the current URL. + Future reload() { + return _webViewPlatformController.reload(); + } + + /// Clears all caches used by the [WebView]. + /// + /// The following caches are cleared: + /// 1. Browser HTTP Cache. + /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. + /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. + /// 3. Application cache. + /// 4. Local Storage. + /// + /// Note: Calling this method also triggers a reload. + Future clearCache() async { + await _webViewPlatformController.clearCache(); + return reload(); + } + + /// Update the widget managed by the [WebViewController]. + Future updateWidget(WebView widget) async { + _widget = widget; + await _updateSettings(_webSettingsFromWidget(widget)); + await _updateJavascriptChannels( + _javascriptChannelRegistry.channels.values.toSet()); + } + + Future _updateSettings(WebSettings newSettings) { + final WebSettings update = + _clearUnchangedWebSettings(_settings, newSettings); + _settings = newSettings; + return _webViewPlatformController.updateSettings(update); + } + + Future _updateJavascriptChannels( + Set? newChannels) async { + final Set currentChannels = + _javascriptChannelRegistry.channels.keys.toSet(); + final Set newChannelNames = _extractChannelNames(newChannels); + final Set channelsToAdd = + newChannelNames.difference(currentChannels); + final Set channelsToRemove = + currentChannels.difference(newChannelNames); + if (channelsToRemove.isNotEmpty) { + await _webViewPlatformController + .removeJavascriptChannels(channelsToRemove); + } + if (channelsToAdd.isNotEmpty) { + await _webViewPlatformController.addJavascriptChannels(channelsToAdd); + } + _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); + } + + @visibleForTesting + // ignore: public_member_api_docs + Future evaluateJavascript(String javascriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error(FlutterError( + 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); + } + return _webViewPlatformController.evaluateJavascript(javascriptString); + } + + /// Runs the given JavaScript in the context of the current page. + /// If you are looking for the result, use [runJavascriptReturningResult] instead. + /// The Future completes with an error if a JavaScript error occurred. + /// + /// When running JavaScript in a [WebView], it is best practice to wait for + // the [WebView.onPageFinished] callback. This guarantees all the JavaScript + // embedded in the main frame HTML has been loaded. + Future runJavascript(String javaScriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error(FlutterError( + 'Javascript mode must be enabled/unrestricted when calling runJavascript.')); + } + return _webViewPlatformController.runJavascript(javaScriptString); + } + + /// Runs the given JavaScript in the context of the current page, and returns the result. + /// + /// Returns the evaluation result as a JSON formatted string. + /// The Future completes with an error if a JavaScript error occurred. + /// + /// When evaluating JavaScript in a [WebView], it is best practice to wait for + /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript + /// embedded in the main frame HTML has been loaded. + Future runJavascriptReturningResult(String javaScriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error(FlutterError( + 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); + } + return _webViewPlatformController + .runJavascriptReturningResult(javaScriptString); + } + + /// Returns the title of the currently loaded page. + Future getTitle() { + return _webViewPlatformController.getTitle(); + } + + /// Sets the WebView's content scroll position. + /// + /// The parameters `x` and `y` specify the scroll position in WebView pixels. + Future scrollTo(int x, int y) { + return _webViewPlatformController.scrollTo(x, y); + } + + /// Move the scrolled position of this view. + /// + /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. + Future scrollBy(int x, int y) { + return _webViewPlatformController.scrollBy(x, y); + } + + /// Return the horizontal scroll position, in WebView pixels, of this view. + /// + /// Scroll position is measured from left. + Future getScrollX() { + return _webViewPlatformController.getScrollX(); + } + + /// Return the vertical scroll position, in WebView pixels, of this view. + /// + /// Scroll position is measured from top. + Future getScrollY() { + return _webViewPlatformController.getScrollY(); + } + + // This method assumes that no fields in `currentValue` are null. + WebSettings _clearUnchangedWebSettings( + WebSettings currentValue, WebSettings newValue) { + assert(currentValue.javascriptMode != null); + assert(currentValue.hasNavigationDelegate != null); + assert(currentValue.hasProgressTracking != null); + assert(currentValue.debuggingEnabled != null); + assert(newValue.javascriptMode != null); + assert(newValue.hasNavigationDelegate != null); + assert(newValue.debuggingEnabled != null); + assert(newValue.zoomEnabled != null); + + JavascriptMode? javascriptMode; + bool? hasNavigationDelegate; + bool? hasProgressTracking; + bool? debuggingEnabled; + WebSetting userAgent = const WebSetting.absent(); + bool? zoomEnabled; + if (currentValue.javascriptMode != newValue.javascriptMode) { + javascriptMode = newValue.javascriptMode; + } + if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { + hasNavigationDelegate = newValue.hasNavigationDelegate; + } + if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { + hasProgressTracking = newValue.hasProgressTracking; + } + if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { + debuggingEnabled = newValue.debuggingEnabled; + } + if (currentValue.userAgent != newValue.userAgent) { + userAgent = newValue.userAgent; + } + if (currentValue.zoomEnabled != newValue.zoomEnabled) { + zoomEnabled = newValue.zoomEnabled; + } + + return WebSettings( + javascriptMode: javascriptMode, + hasNavigationDelegate: hasNavigationDelegate, + hasProgressTracking: hasProgressTracking, + debuggingEnabled: debuggingEnabled, + userAgent: userAgent, + zoomEnabled: zoomEnabled, + ); + } + + Set _extractChannelNames(Set? channels) { + final Set channelNames = channels == null + ? {} + : channels.map((JavascriptChannel channel) => channel.name).toSet(); + return channelNames; + } + + // Throws an ArgumentError if `url` is not a valid URL string. + void _validateUrlString(String url) { + try { + final Uri uri = Uri.parse(url); + if (uri.scheme.isEmpty) { + throw ArgumentError('Missing scheme in URL string: "$url"'); + } + } on FormatException catch (e) { + throw ArgumentError(e); + } + } +} + +WebSettings _webSettingsFromWidget(WebView widget) { + return WebSettings( + javascriptMode: widget.javascriptMode, + hasNavigationDelegate: widget.navigationDelegate != null, + hasProgressTracking: widget.onProgress != null, + debuggingEnabled: widget.debuggingEnabled, + gestureNavigationEnabled: widget.gestureNavigationEnabled, + allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, + userAgent: WebSetting.of(widget.userAgent), + zoomEnabled: widget.zoomEnabled, + ); +} + +/// App-facing cookie manager that exposes the correct platform implementation. +class WebViewCookieManager extends WebViewCookieManagerPlatform { + WebViewCookieManager._(); + + /// Returns an instance of the cookie manager for the current platform. + static WebViewCookieManagerPlatform get instance { + if (WebViewCookieManagerPlatform.instance == null) { + if (Platform.isAndroid) { + WebViewCookieManagerPlatform.instance = WebViewOhosCookieManager(); + } else { + throw AssertionError( + 'This platform is currently unsupported for webview_flutter_android.'); + } + } + return WebViewCookieManagerPlatform.instance!; + } +} diff --git a/ohos/automatic_webview_flutter_test/lib/main.dart b/ohos/automatic_webview_flutter_test/lib/main.dart new file mode 100644 index 00000000..4a84e0f2 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/lib/main.dart @@ -0,0 +1,809 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +void main() { + runApp(const MaterialApp(home: WebViewExample())); +} + +const String kNavigationExamplePage = ''' + +Navigation Delegate Example + +

+The navigation delegate is set to block navigation to the youtube website. +

+ + + +'''; + +const String kLocalExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + +'''; + +const String kTransparentBackgroundPage = ''' + + + + Transparent background test + + + +
+

Transparent background test

+
+
+ + +'''; + +const String kLogExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This page is used to test the forwarding of console logs to Dart. +

+ + + +
+ + + + + +
+ + + +'''; + +const String kAlertTestPage = ''' + + + + + + + +

Click the following button to see the effect

+
+ + + +
+ + +'''; + +class WebViewExample extends StatefulWidget { + const WebViewExample({super.key, this.cookieManager}); + + final PlatformWebViewCookieManager? cookieManager; + + @override + State createState() => _WebViewExampleState(); +} + +class _WebViewExampleState extends State { + late final PlatformWebViewController _controller; + + @override + void initState() { + super.initState(); + + _controller = PlatformWebViewController( + AndroidWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x80000000)) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnProgress((int progress) { + debugPrint('WebView is loading (progress : $progress%)'); + }) + ..setOnPageStarted((String url) { + debugPrint('Page started loading: $url'); + }) + ..setOnPageFinished((String url) { + debugPrint('Page finished loading: $url'); + }) + ..setOnWebResourceError((WebResourceError error) { + debugPrint(''' +Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + url: ${error.url} + '''); + }) + ..setOnNavigationRequest((NavigationRequest request) { + if (request.url.contains('pub.dev')) { + debugPrint('blocking navigation to ${request.url}'); + return NavigationDecision.prevent; + } + debugPrint('allowing navigation to ${request.url}'); + return NavigationDecision.navigate; + }) + ..setOnUrlChange((UrlChange change) { + debugPrint('url change to ${change.url}'); + }) + ..setOnHttpAuthRequest((HttpAuthRequest request) { + openDialog(request); + }), + ) + ..addJavaScriptChannel(JavaScriptChannelParams( + name: 'Toaster', + onMessageReceived: (JavaScriptMessage message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message.message)), + ); + }, + )) + ..setOnPlatformPermissionRequest( + (PlatformWebViewPermissionRequest request) { + debugPrint( + 'requesting permissions for ${request.types.map((WebViewPermissionResourceType type) => type.name)}', + ); + request.grant(); + }, + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF4CAF50), + appBar: AppBar( + title: const Text('Flutter WebView example'), + // This drop down menu demonstrates that Flutter widgets can be shown over the web view. + actions: [ + NavigationControls(webViewController: _controller), + SampleMenu( + webViewController: _controller, + cookieManager: widget.cookieManager, + ), + ], + ), + body: PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: _controller), + ).build(context), + floatingActionButton: favoriteButton(), + ); + } + + Widget favoriteButton() { + return FloatingActionButton( + onPressed: () async { + final String? url = await _controller.currentUrl(); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } + }, + child: const Icon(Icons.favorite), + ); + } + + Future openDialog(HttpAuthRequest httpRequest) async { + final TextEditingController usernameTextController = + TextEditingController(); + final TextEditingController passwordTextController = + TextEditingController(); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: const InputDecoration(labelText: 'Username'), + autofocus: true, + controller: usernameTextController, + ), + TextField( + decoration: const InputDecoration(labelText: 'Password'), + controller: passwordTextController, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + httpRequest.onProceed( + WebViewCredential( + user: usernameTextController.text, + password: passwordTextController.text, + ), + ); + Navigator.of(context).pop(); + }, + child: const Text('Authenticate'), + ), + ], + ); + }, + ); + } +} + +enum MenuOptions { + showUserAgent, + listCookies, + clearCookies, + addToCache, + listCache, + clearCache, + navigationDelegate, + doPostRequest, + loadLocalFile, + loadFlutterAsset, + loadHtmlString, + transparentBackground, + setCookie, + videoExample, + logExample, + basicAuthentication, + javaScriptAlert, +} + +class SampleMenu extends StatelessWidget { + SampleMenu({ + super.key, + required this.webViewController, + PlatformWebViewCookieManager? cookieManager, + }) : cookieManager = cookieManager ?? + PlatformWebViewCookieManager( + const PlatformWebViewCookieManagerCreationParams(), + ); + + final PlatformWebViewController webViewController; + late final PlatformWebViewCookieManager cookieManager; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + key: const ValueKey('ShowPopupMenu'), + onSelected: (MenuOptions value) { + switch (value) { + case MenuOptions.showUserAgent: + _onShowUserAgent(); + break; + case MenuOptions.listCookies: + _onListCookies(context); + break; + case MenuOptions.clearCookies: + _onClearCookies(context); + break; + case MenuOptions.addToCache: + _onAddToCache(context); + break; + case MenuOptions.listCache: + _onListCache(); + break; + case MenuOptions.clearCache: + _onClearCache(context); + break; + case MenuOptions.navigationDelegate: + _onNavigationDelegateExample(); + break; + case MenuOptions.doPostRequest: + _onDoPostRequest(); + break; + case MenuOptions.loadLocalFile: + _onLoadLocalFileExample(); + break; + case MenuOptions.loadFlutterAsset: + _onLoadFlutterAssetExample(); + break; + case MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(); + break; + case MenuOptions.transparentBackground: + _onTransparentBackground(); + break; + case MenuOptions.setCookie: + _onSetCookie(); + break; + case MenuOptions.videoExample: + _onVideoExample(context); + break; + case MenuOptions.logExample: + _onLogExample(); + break; + case MenuOptions.basicAuthentication: + _promptForUrl(context); + break; + case MenuOptions.javaScriptAlert: + _onJavaScriptAlertExample(context); + break; + } + }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: MenuOptions.showUserAgent, + child: Text('Show user agent'), + ), + const PopupMenuItem( + value: MenuOptions.listCookies, + child: Text('List cookies'), + ), + const PopupMenuItem( + value: MenuOptions.clearCookies, + child: Text('Clear cookies'), + ), + const PopupMenuItem( + value: MenuOptions.addToCache, + child: Text('Add to cache'), + ), + const PopupMenuItem( + value: MenuOptions.listCache, + child: Text('List cache'), + ), + const PopupMenuItem( + value: MenuOptions.clearCache, + child: Text('Clear cache'), + ), + const PopupMenuItem( + value: MenuOptions.navigationDelegate, + child: Text('Navigation Delegate example'), + ), + const PopupMenuItem( + value: MenuOptions.doPostRequest, + child: Text('Post Request'), + ), + const PopupMenuItem( + value: MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem( + value: MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), + const PopupMenuItem( + value: MenuOptions.loadFlutterAsset, + child: Text('Load Flutter Asset'), + ), + const PopupMenuItem( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), + const PopupMenuItem( + key: ValueKey('ShowTransparentBackgroundExample'), + value: MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + const PopupMenuItem( + value: MenuOptions.logExample, + child: Text('Log example'), + ), + const PopupMenuItem( + value: MenuOptions.videoExample, + child: Text('Video example'), + ), + const PopupMenuItem( + value: MenuOptions.basicAuthentication, + child: Text('Basic Authentication Example'), + ), + const PopupMenuItem( + value: MenuOptions.javaScriptAlert, + child: Text('JavaScript Alert Example'), + ), + ], + ); + } + + Future _onShowUserAgent() { + // Send a message with the user agent string to the Toaster JavaScript channel we registered + // with the WebView. + return webViewController.runJavaScript( + 'Toaster.postMessage("User Agent: " + navigator.userAgent);', + ); + } + + Future _onListCookies(BuildContext context) async { + final String cookies = await webViewController + .runJavaScriptReturningResult('document.cookie') as String; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } + } + + Future _onAddToCache(BuildContext context) async { + await webViewController.runJavaScript( + 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', + ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } + } + + Future _onListCache() { + return webViewController.runJavaScript('caches.keys()' + // ignore: missing_whitespace_between_adjacent_strings + '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' + '.then((caches) => Toaster.postMessage(caches))'); + } + + Future _onClearCache(BuildContext context) async { + await webViewController.clearCache(); + await webViewController.clearLocalStorage(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } + } + + Future _onClearCookies(BuildContext context) async { + final bool hadCookies = await cookieManager.clearCookies(); + String message = 'There were cookies. Now, they are gone!'; + if (!hadCookies) { + message = 'There are no cookies.'; + } + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } + } + + Future _onNavigationDelegateExample() { + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(kNavigationExamplePage), + ); + return webViewController.loadRequest( + LoadRequestParams( + uri: Uri.parse('data:text/html;base64,$contentBase64'), + ), + ); + } + + Future _onSetCookie() async { + await cookieManager.setCookie( + const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'httpbin.org', + path: '/anything', + ), + ); + await webViewController.loadRequest(LoadRequestParams( + uri: Uri.parse('https://httpbin.org/anything'), + )); + } + + Future _onVideoExample(BuildContext context) { + final AndroidWebViewController androidController = + webViewController as AndroidWebViewController; + // #docregion fullscreen_example + androidController.setCustomWidgetCallbacks( + onShowCustomWidget: (Widget widget, OnHideCustomWidgetCallback callback) { + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => widget, + fullscreenDialog: true, + )); + }, + onHideCustomWidget: () { + Navigator.of(context).pop(); + }, + ); + // #enddocregion fullscreen_example + + return androidController.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://www.youtube.com/watch?v=4AoFA19gbLo'), + ), + ); + } + + Future _onDoPostRequest() { + return webViewController.loadRequest(LoadRequestParams( + uri: Uri.parse('https://httpbin.org/post'), + method: LoadRequestMethod.post, + headers: const { + 'foo': 'bar', + 'Content-Type': 'text/plain', + }, + body: Uint8List.fromList('Test Body'.codeUnits), + )); + } + + Future _onLoadLocalFileExample() async { + final String pathToIndex = await _prepareLocalFile(); + await webViewController.loadFile(pathToIndex); + } + + Future _onLoadFlutterAssetExample() { + return webViewController.loadFlutterAsset('assets/www/index.html'); + } + + Future _onLoadHtmlStringExample() { + return webViewController.loadHtmlString(kLocalExamplePage); + } + + Future _onTransparentBackground() { + return webViewController.loadHtmlString(kTransparentBackgroundPage); + } + + Future _onJavaScriptAlertExample(BuildContext context) { + webViewController.setOnJavaScriptAlertDialog( + (JavaScriptAlertDialogRequest request) async { + await _showAlert(context, request.message); + }); + + webViewController.setOnJavaScriptConfirmDialog( + (JavaScriptConfirmDialogRequest request) async { + final bool result = await _showConfirm(context, request.message); + return result; + }); + + webViewController.setOnJavaScriptTextInputDialog( + (JavaScriptTextInputDialogRequest request) async { + final String result = + await _showTextInput(context, request.message, request.defaultText); + return result; + }); + + return webViewController.loadHtmlString(kAlertTestPage); + } + + Widget _getCookieList(String cookies) { + if (cookies == '""') { + return Container(); + } + final List cookieList = cookies.split(';'); + final Iterable cookieWidgets = + cookieList.map((String cookie) => Text(cookie)); + return Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: cookieWidgets.toList(), + ); + } + + static Future _prepareLocalFile() async { + final String tmpDir = (await getTemporaryDirectory()).path; + final File indexFile = File( + {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator)); + + await indexFile.create(recursive: true); + await indexFile.writeAsString(kLocalExamplePage); + + return indexFile.path; + } + + Future _onLogExample() { + webViewController + .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) { + debugPrint( + '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}'); + }); + return webViewController.loadHtmlString(kLogExamplePage); + } + + Future _promptForUrl(BuildContext context) { + final TextEditingController urlTextController = TextEditingController(); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Input URL to visit'), + content: TextField( + decoration: const InputDecoration(labelText: 'URL'), + autofocus: true, + controller: urlTextController, + ), + actions: [ + TextButton( + onPressed: () { + if (urlTextController.text.isNotEmpty) { + final Uri? uri = Uri.tryParse(urlTextController.text); + if (uri != null && uri.scheme.isNotEmpty) { + webViewController.loadRequest( + LoadRequestParams(uri: uri), + ); + Navigator.pop(context); + } + } + }, + child: const Text('Visit'), + ), + ], + ); + }, + ); + } + + Future _showAlert(BuildContext context, String message) async { + return showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop(); + }, + child: const Text('OK')) + ], + ); + }); + } + + Future _showConfirm(BuildContext context, String message) async { + return await showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop(false); + }, + child: const Text('Cancel')), + TextButton( + onPressed: () { + Navigator.of(ctx).pop(true); + }, + child: const Text('OK')), + ], + ); + }) ?? + false; + } + + Future _showTextInput( + BuildContext context, String message, String? defaultText) async { + return await showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop('Text test'); + }, + child: const Text('Enter')), + ], + ); + }) ?? + ''; + } +} + +class NavigationControls extends StatelessWidget { + const NavigationControls({super.key, required this.webViewController}); + + final PlatformWebViewController webViewController; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () async { + if (await webViewController.canGoBack()) { + await webViewController.goBack(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: () async { + if (await webViewController.canGoForward()) { + await webViewController.goForward(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.replay), + onPressed: () => webViewController.reload(), + ), + ], + ); + } +} diff --git a/ohos/automatic_webview_flutter_test/pubspec.yaml b/ohos/automatic_webview_flutter_test/pubspec.yaml new file mode 100644 index 00000000..2bd4e2c8 --- /dev/null +++ b/ohos/automatic_webview_flutter_test/pubspec.yaml @@ -0,0 +1,65 @@ +# Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: webview_flutter_ohos_test +description: Demonstrates how to use the webview_flutter_ohos plugin. +publish_to: none + +environment: + sdk: ">=2.19.0 <4.0.0" + flutter: ">=3.7.0" + +dependencies: + flutter: + sdk: flutter + integration_test: + sdk: flutter + path_provider: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/path_provider/path_provider" + path_provider_platform_interface: ^2.0.0 + webview_flutter_ohos: + # When depending on this package from a real application you should use: + # webview_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/webview_flutter/webview_flutter_ohos" + +dependency_overrides: + path_provider_ohos: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/path_provider/path_provider_ohos" + webview_flutter_platform_interface: + git: + url: "https://gitee.com/openharmony-sig/flutter_packages.git" + path: "packages/webview_flutter/webview_flutter_platform_interface" + +dev_dependencies: + espresso: ^0.2.0 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true + assets: + - assets/sample_audio.ogg + - assets/sample_video.mp4 + - assets/www/index.html + - assets/www/styles/style.css -- Gitee From 8c7766d7e48c6f3b5cf25f63a434e598f6b904e0 Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:32:59 +0800 Subject: [PATCH 16/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4webview=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../assets/sample_audio.ogg | Bin 0 -> 36870 bytes .../assets/www/index.html | 20 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 ohos/automatic_webview_flutter_test/assets/sample_audio.ogg create mode 100644 ohos/automatic_webview_flutter_test/assets/www/index.html diff --git a/ohos/automatic_webview_flutter_test/assets/sample_audio.ogg b/ohos/automatic_webview_flutter_test/assets/sample_audio.ogg new file mode 100644 index 0000000000000000000000000000000000000000..27e17104277b38ae70d3936f5afb4ea32477ec22 GIT binary patch literal 36870 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 XfG+?r+!9a#U;+;`01|XOAOHdYynO-T literal 0 HcmV?d00001 diff --git a/ohos/automatic_webview_flutter_test/assets/www/index.html b/ohos/automatic_webview_flutter_test/assets/www/index.html new file mode 100644 index 00000000..9895dd3c --- /dev/null +++ b/ohos/automatic_webview_flutter_test/assets/www/index.html @@ -0,0 +1,20 @@ + + + + +Load file or HTML string example + + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + \ No newline at end of file -- Gitee From e78577d07d61b0a5a8d751f1155f8da03a1c072a Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:34:49 +0800 Subject: [PATCH 17/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- ohos/node_test_server/bats/cameraTest.bat | 52 ++++++++++++ .../bats/camera_test/cameraTest.bat | 13 +++ .../bats/camera_test/cameraTest_0.bat | 13 +++ .../bats/camera_test/cameraTest_1.bat | 13 +++ .../bats/camera_test/cameraTest_2.bat | 13 +++ .../bats/camera_test/cameraTest_3.bat | 13 +++ .../bats/camera_test/cameraTest_4.bat | 14 ++++ .../bats/camera_test/cameraTest_5.bat | 13 +++ .../bats/camera_test/cameraTest_6.bat | 14 ++++ .../bats/camera_test/cameraTest_7.bat | 15 ++++ .../node_test_server/bats/videoPlayerTest.bat | 84 +++++++++++++++++++ .../bats/webviewFlutterTest.bat | 13 +++ ohos/node_test_server/index.bat | 12 +++ 13 files changed, 282 insertions(+) create mode 100644 ohos/node_test_server/bats/cameraTest.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_0.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_1.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_2.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_3.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_4.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_5.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_6.bat create mode 100644 ohos/node_test_server/bats/camera_test/cameraTest_7.bat create mode 100644 ohos/node_test_server/bats/videoPlayerTest.bat create mode 100644 ohos/node_test_server/bats/webviewFlutterTest.bat diff --git a/ohos/node_test_server/bats/cameraTest.bat b/ohos/node_test_server/bats/cameraTest.bat new file mode 100644 index 00000000..f812aa83 --- /dev/null +++ b/ohos/node_test_server/bats/cameraTest.bat @@ -0,0 +1,52 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. + +@REM you also can run the script 'cameraTest.bat', please make sure your environment can run the script smooth +cd ./camera_test +echo 测试camera testcase_00 +start cameraTest.bat +timeout /t 110 + +@REM by some environments, running script 'cameraTest.bat' , appeared frame freezing in somewhere +@REM cd ./camera_test +@REM echo 测试camera testcase_00 +@REM start cameraTest_0.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_01 +@REM start cameraTest_1.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_02 +@REM start cameraTest_2.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_03 +@REM start cameraTest_3.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_04 +@REM start cameraTest_4.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_05 +@REM start cameraTest_5.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_06 +@REM start cameraTest_6.bat +@REM timeout /t 110 +@REM +@REM echo 测试camera testcase_07 +@REM start cameraTest_7.bat +@REM timeout /t 110 \ No newline at end of file diff --git a/ohos/node_test_server/bats/camera_test/cameraTest.bat b/ohos/node_test_server/bats/camera_test/cameraTest.bat new file mode 100644 index 00000000..27c0c05c --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test .\integration_test\camera_test.dart --machine > ..\node_test_server\logs\camera_test.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_0.bat b/ohos/node_test_server/bats/camera_test/cameraTest_0.bat new file mode 100644 index 00000000..55823a45 --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_0.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "Pause and resume video recording" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_0.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_1.bat b/ohos/node_test_server/bats/camera_test/cameraTest_1.bat new file mode 100644 index 00000000..2cb41d6c --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_1.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "Set description while recording" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_1.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_2.bat b/ohos/node_test_server/bats/camera_test/cameraTest_2.bat new file mode 100644 index 00000000..952c41eb --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_2.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "Set description" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_2.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_3.bat b/ohos/node_test_server/bats/camera_test/cameraTest_3.bat new file mode 100644 index 00000000..c443ac14 --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_3.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "image streaming" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_3.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_4.bat b/ohos/node_test_server/bats/camera_test/cameraTest_4.bat new file mode 100644 index 00000000..bc5df3a0 --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_4.bat @@ -0,0 +1,14 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "recording with image stream" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_4.txt + diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_5.bat b/ohos/node_test_server/bats/camera_test/cameraTest_5.bat new file mode 100644 index 00000000..e7000138 --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_5.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "takePicture" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_5.txt diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_6.bat b/ohos/node_test_server/bats/camera_test/cameraTest_6.bat new file mode 100644 index 00000000..fbc258b7 --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_6.bat @@ -0,0 +1,14 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "previewerMode" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_6.txt + diff --git a/ohos/node_test_server/bats/camera_test/cameraTest_7.bat b/ohos/node_test_server/bats/camera_test/cameraTest_7.bat new file mode 100644 index 00000000..f586ddb4 --- /dev/null +++ b/ohos/node_test_server/bats/camera_test/cameraTest_7.bat @@ -0,0 +1,15 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_camera_test && flutter test --machine --start-paused --plain-name "ExposureMode" integration_test\camera_test.dart > ..\node_test_server\logs\camera_test_7.txt + + diff --git a/ohos/node_test_server/bats/videoPlayerTest.bat b/ohos/node_test_server/bats/videoPlayerTest.bat new file mode 100644 index 00000000..3c772381 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayerTest.bat @@ -0,0 +1,84 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. + +@REM you also can run the script 'videoPlayerTest.bat' like this, please make sure your environment can run the script smooth +cd ./videoPlayer_test +echo 测试videoPlayerTest +start videoPlayerTest.bat +timeout /t 110 + +@REM by some environments, running script 'videoPlayerTest.bat' , appeared frame freezing in somewhere +@REM cd ./videoPlayer_test +@REM echo 测试videoPlayerTest testcase_01 +@REM start videoPlayerTest_01.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_02 +@REM start videoPlayerTest_02.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_03 +@REM start videoPlayerTest_03.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_04 +@REM start videoPlayerTest_04.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_05 +@REM start videoPlayerTest_05.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_06 +@REM start videoPlayerTest_06.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_07 +@REM start videoPlayerTest_07.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_08 +@REM start videoPlayerTest_08.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_09 +@REM start videoPlayerTest_09.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_10 +@REM start videoPlayerTest_10.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_11 +@REM start videoPlayerTest_11.bat +@REM timeout /t 110 +@REM +@REM @REM echo 测试videoPlayerTest testcase_12 +@REM @REM start videoPlayerTest_12.bat +@REM @REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_13 +@REM start videoPlayerTest_13.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_14 +@REM start videoPlayerTest_14.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_15 +@REM start videoPlayerTest_15.bat +@REM timeout /t 110 +@REM +@REM echo 测试videoPlayerTest testcase_16 +@REM start videoPlayerTest_16.bat +@REM timeout /t 110 diff --git a/ohos/node_test_server/bats/webviewFlutterTest.bat b/ohos/node_test_server/bats/webviewFlutterTest.bat new file mode 100644 index 00000000..f91f3147 --- /dev/null +++ b/ohos/node_test_server/bats/webviewFlutterTest.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\automatic_webview_flutter_test && flutter test .\integration_test\webview_flutter_test.dart --machine > ..\node_test_server\logs\webview_flutter_test.txt \ No newline at end of file diff --git a/ohos/node_test_server/index.bat b/ohos/node_test_server/index.bat index 34036ffb..69b9b224 100644 --- a/ohos/node_test_server/index.bat +++ b/ohos/node_test_server/index.bat @@ -25,5 +25,17 @@ echo 测试keyChannel库 start keyChannelTest.bat timeout /t 130 +echo 测试webview_flutter库 +start webviewFlutterTest.bat +timeout /t 130 + +echo 测试camera库 +start cameraTest.bat +timeout /t 1000 + +echo 测试video_player库 +start videoPlayerTest.bat +timeout /t 2000 + echo 生成表格 start createxlsx.bat -- Gitee From 8a637c2afbbaf7184f5ff3470cc8bb2607da4971 Mon Sep 17 00:00:00 2001 From: xiaozn Date: Wed, 13 Nov 2024 19:35:18 +0800 Subject: [PATCH 18/18] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xiaozn --- .../bats/videoPlayer_test/videoPlayerTest.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_01.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_02.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_03.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_04.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_05.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_06.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_07.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_08.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_09.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_10.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_11.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_12.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_13.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_14.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_15.bat | 13 +++++++++++++ .../bats/videoPlayer_test/videoPlayerTest_16.bat | 13 +++++++++++++ 17 files changed, 221 insertions(+) create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_01.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_02.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_03.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_04.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_05.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_06.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_07.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_08.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_09.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_10.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_11.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_12.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_13.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_14.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_15.bat create mode 100644 ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_16.bat diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest.bat new file mode 100644 index 00000000..e8eb00db --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test .\integration_test\video_player_test.dart --machine > ..\node_test_server\logs\video_player_test.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_01.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_01.bat new file mode 100644 index 00000000..94a8f3b0 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_01.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - can be initialized" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_1.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_02.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_02.bat new file mode 100644 index 00000000..788cb239 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_02.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - live stream duration != 0" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_2.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_03.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_03.bat new file mode 100644 index 00000000..7ed81034 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_03.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - can be played" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_3.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_04.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_04.bat new file mode 100644 index 00000000..e0782a4c --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_04.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - can seek" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_4.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_05.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_05.bat new file mode 100644 index 00000000..66130fb0 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_05.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - can be paused" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_5.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_06.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_06.bat new file mode 100644 index 00000000..b2efd4ae --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_06.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - can be loop play" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_6.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_07.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_07.bat new file mode 100644 index 00000000..3a95b591 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_07.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset videos - can set backplayspeed" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_7.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_08.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_08.bat new file mode 100644 index 00000000..5f7c3d24 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_08.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "stay paused when seeking after video completed" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_8.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_09.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_09.bat new file mode 100644 index 00000000..6a1b1f73 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_09.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "do not exceed duration on play after video completed" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_9.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_10.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_10.bat new file mode 100644 index 00000000..9f56e178 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_10.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "test video player view with local asset" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_10.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_11.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_11.bat new file mode 100644 index 00000000..fe7ef906 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_11.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "test video player using static file() method as constructor" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_11.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_12.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_12.bat new file mode 100644 index 00000000..00f5645b --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_12.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "reports buffering status" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_12.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_13.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_13.bat new file mode 100644 index 00000000..16450702 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_13.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset audios - can be initialized" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_13.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_14.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_14.bat new file mode 100644 index 00000000..5cb80f47 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_14.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset audios - can be played" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_14.txt \ No newline at end of file diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_15.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_15.bat new file mode 100644 index 00000000..c27d12f8 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_15.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset audios - can seek" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_15.txt diff --git a/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_16.bat b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_16.bat new file mode 100644 index 00000000..6e2290c6 --- /dev/null +++ b/ohos/node_test_server/bats/videoPlayer_test/videoPlayerTest_16.bat @@ -0,0 +1,13 @@ +@REM Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +cd ..\..\..\automatic_video_player_test && flutter test --machine --start-paused --plain-name "asset audios - can be paused" integration_test\video_player_test.dart > ..\node_test_server\logs\video_player_test_16.txt -- Gitee