
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 Foundation enum PreviewCameraError: Error { case noCamera case alreadyStarted case alreadyStopped case torchError(_ error: Error) case cameraError(_ error: Error) case torchWhenStopped case analyzerError(_ error: Error) }
Sau đó tạo class PreviewCamera chứ các hàm để khởi tạo camera trên ios:
import Foundation import AVFoundation public class PreviewCamera: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture { /// Image to be sent to the texture var latestBuffer: CVImageBuffer! /// Capture session of the camera var captureSession: AVCaptureSession! /// The selected camera var device: AVCaptureDevice! /// Texture id of the camera preview for Flutter private var textureId: Int64! /// Default position of camera var 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 = registry super.init() } /// Check permissions for video func checkPermission() -> Int { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .notDetermined: return 0 case .authorized: return 1 default: return 2 } } /// Request permissions for video func requestPermission(_ result: @escaping FlutterResult) { AVCaptureDevice.requestAccess(for: .video, completionHandler: { result($0) }) } /// Gets called when a new image is added to the buffer public 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 = imageBuffer registry?.textureFrameAvailable(textureId) } /// Start scanning for barcodes func 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 device if #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 input do { 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 = true videoPosition = cameraPosition // calls captureOutput() videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) captureSession.addOutput(videoOutput) for connection in videoOutput.connections { connection.videoOrientation = .portrait if 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 texture public func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? { if latestBuffer == nil { return nil } return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer) } struct MobileScannerStartParameters { var width: Double = 0.0 var height: Double = 0.0 var hasTorch = false var textureId: Int64 = 0 } }
Tiếp theo, Mở class SwiftPreviewLibPlugin và sửa lại như class dưới đây:
import Flutter import UIKit import AVFoundation public class SwiftPreviewLibPlugin: NSObject, FlutterPlugin { private let previewCamera :PreviewCamera init(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.back do { 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: