Check log trên android studio ta thấy lỗi:
Tiếp theo để tiến hành triển khai preview camera cho native code IOS, ta mở project example trên xcode bằng cách mở file Runner.xcodeproj. Ta được kết quả như hình:
chúng ta sẽ làm việc chủ yếu trên folder “preview_lib/../../example/ios/.symlinks/ios/Classes/”, folder này tương ứng với folder “preview_lib/ios/Classes” ở trên lib.
Tiếp theo, tạo enum class PreviewCameraError để handle error như sau:
import Foundationenum PreviewCameraError: Error {case noCameracase alreadyStartedcase alreadyStoppedcase torchError(_ error: Error)case cameraError(_ error: Error)case torchWhenStoppedcase analyzerError(_ error: Error)}
Sau đó tạo class PreviewCamera chứ các hàm để khởi tạo camera trên ios:
import Foundationimport AVFoundationpublic class PreviewCamera: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture {/// Image to be sent to the texturevar latestBuffer: CVImageBuffer!/// Capture session of the cameravar captureSession: AVCaptureSession!/// The selected cameravar device: AVCaptureDevice!/// Texture id of the camera preview for Flutterprivate var textureId: Int64!/// Default position of cameravar videoPosition: AVCaptureDevice.Position = AVCaptureDevice.Position.back/// If provided, the Flutter registry will be used to send the output of the CaptureOutput to a Flutter texture.private let registry: FlutterTextureRegistry?init(registry: FlutterTextureRegistry?) {self.registry = registrysuper.init()}/// Check permissions for videofunc checkPermission() -> Int {let status = AVCaptureDevice.authorizationStatus(for: .video)switch status {case .notDetermined:return 0case .authorized:return 1default:return 2}}/// Request permissions for videofunc requestPermission(_ result: @escaping FlutterResult) {AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })}/// Gets called when a new image is added to the bufferpublic func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {print("Failed to get image buffer from sample buffer.")return}latestBuffer = imageBufferregistry?.textureFrameAvailable(textureId)}/// Start scanning for barcodesfunc start(cameraPosition: AVCaptureDevice.Position) throws -> MobileScannerStartParameters {if (device != nil) {throw PreviewCameraError.alreadyStarted}captureSession = AVCaptureSession()textureId = registry?.register(self)print("Thao: \(String(describing: textureId)) \(String(describing: registry)) ")// Open the camera deviceif #available(iOS 10.0, *) {device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: cameraPosition).devices.first} else {device = AVCaptureDevice.devices(for: .video).filter({$0.position == cameraPosition}).first}if (device == nil) {throw PreviewCameraError.noCamera}captureSession.beginConfiguration()// Add device inputdo {let input = try AVCaptureDeviceInput(device: device)captureSession.addInput(input)} catch {throw PreviewCameraError.cameraError(error)}captureSession.sessionPreset = AVCaptureSession.Preset.photo;// Add video output.let videoOutput = AVCaptureVideoDataOutput()videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]videoOutput.alwaysDiscardsLateVideoFrames = truevideoPosition = cameraPosition// calls captureOutput()videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)captureSession.addOutput(videoOutput)for connection in videoOutput.connections {connection.videoOrientation = .portraitif cameraPosition == .front && connection.isVideoMirroringSupported {connection.isVideoMirrored = true}}captureSession.commitConfiguration()captureSession.startRunning()let dimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription)return MobileScannerStartParameters(width: Double(dimensions.height), height: Double(dimensions.width), hasTorch: device.hasTorch, textureId: textureId)}/// Sends output of OutputBuffer to a Flutter texturepublic func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? {if latestBuffer == nil {return nil}return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)}struct MobileScannerStartParameters {var width: Double = 0.0var height: Double = 0.0var hasTorch = falsevar textureId: Int64 = 0}}
Tiếp theo, Mở class SwiftPreviewLibPlugin và sửa lại như class dưới đây:
import Flutterimport UIKitimport AVFoundationpublic class SwiftPreviewLibPlugin: NSObject, FlutterPlugin {private let previewCamera :PreviewCamerainit(registry: FlutterTextureRegistry) {self.previewCamera = PreviewCamera(registry: registry)super.init()}public static func register(with registrar: FlutterPluginRegistrar) {let methodChannel = FlutterMethodChannel(name: "com.demo.preview_camera/method", binaryMessenger: registrar.messenger())let instance = SwiftPreviewLibPlugin(registry: registrar.textures())registrar.addMethodCallDelegate(instance, channel: methodChannel)}public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {switch call.method {case "state":result(previewCamera.checkPermission())case "request":AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) })case "start":start(call, result)default:result(FlutterMethodNotImplemented)}}private func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {print("start")let position = AVCaptureDevice.Position.backdo {let parameters = try previewCamera.start(cameraPosition: position)result(["textureId": parameters.textureId, "size": ["width": parameters.width, "height": parameters.height], "torchable": parameters.hasTorch])} catch PreviewCameraError.alreadyStarted {result(FlutterError(code: "MobileScanner",message: "Called start() while already started!",details: nil))} catch PreviewCameraError.noCamera {result(FlutterError(code: "MobileScanner",message: "No camera found or failed to open camera!",details: nil))} catch PreviewCameraError.torchError(let error) {result(FlutterError(code: "MobileScanner",message: "Error occured when setting toch!",details: error))} catch PreviewCameraError.cameraError(let error) {result(FlutterError(code: "MobileScanner",message: "Error occured when setting up camera!",details: error))} catch {result(FlutterError(code: "MobileScanner",message: "Unknown error occured..",details: nil))}}}
Tiếp theo hãy chạy app trên iphone, ta thấy app bị crash và hiển thị lỗi này dưới log:
Hãy mở file Info.plist trong đường dẫn example/ios/Runner/Info.plist và thêm quyền camera như sau:
<key>NSCameraUsageDescription</key><string>$(PRODUCT_NAME) camera description.</string>
Sau đó hãy chạy lại app lần nữa để xem app còn bị lỗi nữa không. Và ta được kết quả như hình: