[camera] Use Pigeon for Windows C++->Dart (#8001)

Replaces direct method channel usage with Pigeon for C++-to-Dart communication. On the Dart side, this re-uses the same structure used in other platform implementations.

Part of http://github.com/flutter/flutter/issues/117905
diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md
index e9f281b..6661f40 100644
--- a/packages/camera/camera_windows/CHANGELOG.md
+++ b/packages/camera/camera_windows/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.2.5+1
 
+* Updates C++ to Dart communication to use Pigeon.
 * Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
 
 ## 0.2.5
diff --git a/packages/camera/camera_windows/lib/camera_windows.dart b/packages/camera/camera_windows/lib/camera_windows.dart
index 6166c99..bdbfc17 100644
--- a/packages/camera/camera_windows/lib/camera_windows.dart
+++ b/packages/camera/camera_windows/lib/camera_windows.dart
@@ -27,8 +27,11 @@
   /// Interface for calling host-side code.
   final CameraApi _hostApi;
 
-  /// Camera specific method channels to allow communicating with specific cameras.
-  final Map<int, MethodChannel> _cameraChannels = <int, MethodChannel>{};
+  /// The per-camera handlers for messages that should be rebroadcast to
+  /// clients as [CameraEvent]s.
+  @visibleForTesting
+  final Map<int, HostCameraMessageHandler> hostCameraHandlers =
+      <int, HostCameraMessageHandler>{};
 
   // The stream to receive frames from the native code.
   StreamSubscription<dynamic>? _platformImageStreamSubscription;
@@ -105,15 +108,8 @@
     int cameraId, {
     ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown,
   }) async {
-    /// Creates channel for camera events.
-    _cameraChannels.putIfAbsent(cameraId, () {
-      final MethodChannel channel =
-          MethodChannel('plugins.flutter.io/camera_windows/camera$cameraId');
-      channel.setMethodCallHandler(
-        (MethodCall call) => handleCameraMethodCall(call, cameraId),
-      );
-      return channel;
-    });
+    hostCameraHandlers.putIfAbsent(cameraId,
+        () => HostCameraMessageHandler(cameraId, cameraEventStreamController));
 
     final PlatformSize reply;
     try {
@@ -140,11 +136,7 @@
     await _hostApi.dispose(cameraId);
 
     // Destroy method channel after camera is disposed to be able to handle last messages.
-    if (_cameraChannels.containsKey(cameraId)) {
-      final MethodChannel? cameraChannel = _cameraChannels[cameraId];
-      cameraChannel?.setMethodCallHandler(null);
-      _cameraChannels.remove(cameraId);
-    }
+    hostCameraHandlers.remove(cameraId)?.dispose();
   }
 
   @override
@@ -398,33 +390,6 @@
     return Texture(textureId: cameraId);
   }
 
-  /// Converts messages received from the native platform into camera events.
-  ///
-  /// This is only exposed for test purposes. It shouldn't be used by clients
-  /// of the plugin as it may break or change at any time.
-  @visibleForTesting
-  Future<dynamic> handleCameraMethodCall(MethodCall call, int cameraId) async {
-    switch (call.method) {
-      case 'camera_closing':
-        cameraEventStreamController.add(
-          CameraClosingEvent(
-            cameraId,
-          ),
-        );
-      case 'error':
-        final Map<String, Object?> arguments =
-            (call.arguments as Map<Object?, Object?>).cast<String, Object?>();
-        cameraEventStreamController.add(
-          CameraErrorEvent(
-            cameraId,
-            arguments['description']! as String,
-          ),
-        );
-      default:
-        throw UnimplementedError();
-    }
-  }
-
   /// Returns a [MediaSettings]'s Pigeon representation.
   PlatformMediaSettings _pigeonMediaSettings(MediaSettings? settings) {
     return PlatformMediaSettings(
@@ -467,3 +432,35 @@
     return PlatformResolutionPreset.max;
   }
 }
+
+/// Callback handler for camera-level events from the platform host.
+@visibleForTesting
+class HostCameraMessageHandler implements CameraEventApi {
+  /// Creates a new handler that listens for events from camera [cameraId], and
+  /// broadcasts them to [streamController].
+  HostCameraMessageHandler(this.cameraId, this.streamController) {
+    CameraEventApi.setUp(this, messageChannelSuffix: cameraId.toString());
+  }
+
+  /// Removes the handler for native messages.
+  void dispose() {
+    CameraEventApi.setUp(null, messageChannelSuffix: cameraId.toString());
+  }
+
+  /// The camera ID this handler listens for events from.
+  final int cameraId;
+
+  /// The controller used to broadcast camera events coming from the
+  /// host platform.
+  final StreamController<CameraEvent> streamController;
+
+  @override
+  void error(String message) {
+    streamController.add(CameraErrorEvent(cameraId, message));
+  }
+
+  @override
+  void cameraClosing() {
+    streamController.add(CameraClosingEvent(cameraId));
+  }
+}
diff --git a/packages/camera/camera_windows/lib/src/messages.g.dart b/packages/camera/camera_windows/lib/src/messages.g.dart
index bb364ae..9930549 100644
--- a/packages/camera/camera_windows/lib/src/messages.g.dart
+++ b/packages/camera/camera_windows/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
 // 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.
-// Autogenerated from Pigeon (v21.0.0), do not edit directly.
+// Autogenerated from Pigeon (v22.6.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
 
@@ -18,6 +18,17 @@
   );
 }
 
+List<Object?> wrapResponse(
+    {Object? result, PlatformException? error, bool empty = false}) {
+  if (empty) {
+    return <Object?>[];
+  }
+  if (error == null) {
+    return <Object?>[result];
+  }
+  return <Object?>[error.code, error.message, error.details];
+}
+
 /// Pigeon version of platform interface's ResolutionPreset.
 enum PlatformResolutionPreset {
   low,
@@ -101,15 +112,18 @@
   const _PigeonCodec();
   @override
   void writeValue(WriteBuffer buffer, Object? value) {
-    if (value is PlatformMediaSettings) {
+    if (value is int) {
+      buffer.putUint8(4);
+      buffer.putInt64(value);
+    } else if (value is PlatformResolutionPreset) {
       buffer.putUint8(129);
-      writeValue(buffer, value.encode());
-    } else if (value is PlatformSize) {
+      writeValue(buffer, value.index);
+    } else if (value is PlatformMediaSettings) {
       buffer.putUint8(130);
       writeValue(buffer, value.encode());
-    } else if (value is PlatformResolutionPreset) {
+    } else if (value is PlatformSize) {
       buffer.putUint8(131);
-      writeValue(buffer, value.index);
+      writeValue(buffer, value.encode());
     } else {
       super.writeValue(buffer, value);
     }
@@ -119,12 +133,12 @@
   Object? readValueOfType(int type, ReadBuffer buffer) {
     switch (type) {
       case 129:
-        return PlatformMediaSettings.decode(readValue(buffer)!);
-      case 130:
-        return PlatformSize.decode(readValue(buffer)!);
-      case 131:
         final int? value = readValue(buffer) as int?;
         return value == null ? null : PlatformResolutionPreset.values[value];
+      case 130:
+        return PlatformMediaSettings.decode(readValue(buffer)!);
+      case 131:
+        return PlatformSize.decode(readValue(buffer)!);
       default:
         return super.readValueOfType(type, buffer);
     }
@@ -137,124 +151,124 @@
   /// BinaryMessenger will be used which routes to the host platform.
   CameraApi(
       {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
-      : __pigeon_binaryMessenger = binaryMessenger,
-        __pigeon_messageChannelSuffix =
+      : pigeonVar_binaryMessenger = binaryMessenger,
+        pigeonVar_messageChannelSuffix =
             messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
-  final BinaryMessenger? __pigeon_binaryMessenger;
+  final BinaryMessenger? pigeonVar_binaryMessenger;
 
   static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
 
-  final String __pigeon_messageChannelSuffix;
+  final String pigeonVar_messageChannelSuffix;
 
   /// Returns the names of all of the available capture devices.
-  Future<List<String?>> getAvailableCameras() async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.getAvailableCameras$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+  Future<List<String>> getAvailableCameras() async {
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.getAvailableCameras$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(null) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(null) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
-    } else if (__pigeon_replyList[0] == null) {
+    } else if (pigeonVar_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (__pigeon_replyList[0] as List<Object?>?)!.cast<String?>();
+      return (pigeonVar_replyList[0] as List<Object?>?)!.cast<String>();
     }
   }
 
   /// Creates a camera instance for the given device name and settings.
   Future<int> create(String cameraName, PlatformMediaSettings settings) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.create$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.create$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList = await __pigeon_channel
+    final List<Object?>? pigeonVar_replyList = await pigeonVar_channel
         .send(<Object?>[cameraName, settings]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
-    } else if (__pigeon_replyList[0] == null) {
+    } else if (pigeonVar_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (__pigeon_replyList[0] as int?)!;
+      return (pigeonVar_replyList[0] as int?)!;
     }
   }
 
   /// Initializes a camera, and returns the size of its preview.
   Future<PlatformSize> initialize(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.initialize$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.initialize$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
-    } else if (__pigeon_replyList[0] == null) {
+    } else if (pigeonVar_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (__pigeon_replyList[0] as PlatformSize?)!;
+      return (pigeonVar_replyList[0] as PlatformSize?)!;
     }
   }
 
   /// Disposes a camera that is no longer in use.
   Future<void> dispose(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.dispose$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.dispose$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
     } else {
       return;
@@ -264,53 +278,53 @@
   /// Takes a picture with the given camera, and returns the path to the
   /// resulting file.
   Future<String> takePicture(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.takePicture$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.takePicture$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
-    } else if (__pigeon_replyList[0] == null) {
+    } else if (pigeonVar_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (__pigeon_replyList[0] as String?)!;
+      return (pigeonVar_replyList[0] as String?)!;
     }
   }
 
   /// Starts recording video with the given camera.
   Future<void> startVideoRecording(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.startVideoRecording$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.startVideoRecording$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
     } else {
       return;
@@ -320,53 +334,53 @@
   /// Finishes recording video with the given camera, and returns the path to
   /// the resulting file.
   Future<String> stopVideoRecording(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.stopVideoRecording$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.stopVideoRecording$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
-    } else if (__pigeon_replyList[0] == null) {
+    } else if (pigeonVar_replyList[0] == null) {
       throw PlatformException(
         code: 'null-error',
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (__pigeon_replyList[0] as String?)!;
+      return (pigeonVar_replyList[0] as String?)!;
     }
   }
 
   /// Starts the image stream for the given camera.
   Future<void> startImageStream(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.startImageStream$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
     } else {
       return;
@@ -375,23 +389,23 @@
 
   /// Stops the image stream for the given camera.
   Future<void> stopImageStream(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.stopImageStream$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
     } else {
       return;
@@ -400,23 +414,23 @@
 
   /// Starts the preview stream for the given camera.
   Future<void> pausePreview(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.pausePreview$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.pausePreview$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
     } else {
       return;
@@ -425,26 +439,97 @@
 
   /// Resumes the preview stream for the given camera.
   Future<void> resumePreview(int cameraId) async {
-    final String __pigeon_channelName =
-        'dev.flutter.pigeon.camera_windows.CameraApi.resumePreview$__pigeon_messageChannelSuffix';
-    final BasicMessageChannel<Object?> __pigeon_channel =
+    final String pigeonVar_channelName =
+        'dev.flutter.pigeon.camera_windows.CameraApi.resumePreview$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel =
         BasicMessageChannel<Object?>(
-      __pigeon_channelName,
+      pigeonVar_channelName,
       pigeonChannelCodec,
-      binaryMessenger: __pigeon_binaryMessenger,
+      binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final List<Object?>? __pigeon_replyList =
-        await __pigeon_channel.send(<Object?>[cameraId]) as List<Object?>?;
-    if (__pigeon_replyList == null) {
-      throw _createConnectionError(__pigeon_channelName);
-    } else if (__pigeon_replyList.length > 1) {
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_channel.send(<Object?>[cameraId]) as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
       throw PlatformException(
-        code: __pigeon_replyList[0]! as String,
-        message: __pigeon_replyList[1] as String?,
-        details: __pigeon_replyList[2],
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
       );
     } else {
       return;
     }
   }
 }
+
+abstract class CameraEventApi {
+  static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
+
+  /// Called when the camera instance is closing on the native side.
+  void cameraClosing();
+
+  /// Called when a camera error occurs on the native side.
+  void error(String errorMessage);
+
+  static void setUp(
+    CameraEventApi? api, {
+    BinaryMessenger? binaryMessenger,
+    String messageChannelSuffix = '',
+  }) {
+    messageChannelSuffix =
+        messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
+    {
+      final BasicMessageChannel<
+          Object?> pigeonVar_channel = BasicMessageChannel<
+              Object?>(
+          'dev.flutter.pigeon.camera_windows.CameraEventApi.cameraClosing$messageChannelSuffix',
+          pigeonChannelCodec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        pigeonVar_channel.setMessageHandler(null);
+      } else {
+        pigeonVar_channel.setMessageHandler((Object? message) async {
+          try {
+            api.cameraClosing();
+            return wrapResponse(empty: true);
+          } on PlatformException catch (e) {
+            return wrapResponse(error: e);
+          } catch (e) {
+            return wrapResponse(
+                error: PlatformException(code: 'error', message: e.toString()));
+          }
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<
+          Object?> pigeonVar_channel = BasicMessageChannel<
+              Object?>(
+          'dev.flutter.pigeon.camera_windows.CameraEventApi.error$messageChannelSuffix',
+          pigeonChannelCodec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        pigeonVar_channel.setMessageHandler(null);
+      } else {
+        pigeonVar_channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.camera_windows.CameraEventApi.error was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final String? arg_errorMessage = (args[0] as String?);
+          assert(arg_errorMessage != null,
+              'Argument for dev.flutter.pigeon.camera_windows.CameraEventApi.error was null, expected non-null String.');
+          try {
+            api.error(arg_errorMessage!);
+            return wrapResponse(empty: true);
+          } on PlatformException catch (e) {
+            return wrapResponse(error: e);
+          } catch (e) {
+            return wrapResponse(
+                error: PlatformException(code: 'error', message: e.toString()));
+          }
+        });
+      }
+    }
+  }
+}
diff --git a/packages/camera/camera_windows/pigeons/messages.dart b/packages/camera/camera_windows/pigeons/messages.dart
index a56a93c..d0970af 100644
--- a/packages/camera/camera_windows/pigeons/messages.dart
+++ b/packages/camera/camera_windows/pigeons/messages.dart
@@ -86,3 +86,12 @@
   @async
   void resumePreview(int cameraId);
 }
+
+@FlutterApi()
+abstract class CameraEventApi {
+  /// Called when the camera instance is closing on the native side.
+  void cameraClosing();
+
+  /// Called when a camera error occurs on the native side.
+  void error(String errorMessage);
+}
diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml
index 4ad6878..2ba530a 100644
--- a/packages/camera/camera_windows/pubspec.yaml
+++ b/packages/camera/camera_windows/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin for getting information about and controlling the camera on Windows.
 repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.2.5
+version: 0.2.5+1
 
 environment:
   sdk: ^3.3.0
@@ -29,7 +29,7 @@
   flutter_test:
     sdk: flutter
   mockito: ^5.4.4
-  pigeon: ^21.0.0
+  pigeon: ^22.6.0
 
 topics:
   - camera
diff --git a/packages/camera/camera_windows/test/camera_windows_test.dart b/packages/camera/camera_windows/test/camera_windows_test.dart
index ca7e7eb..7d21af5 100644
--- a/packages/camera/camera_windows/test/camera_windows_test.dart
+++ b/packages/camera/camera_windows/test/camera_windows_test.dart
@@ -218,12 +218,9 @@
 
         // Emit test events
         final CameraClosingEvent event = CameraClosingEvent(cameraId);
-        await plugin.handleCameraMethodCall(
-            MethodCall('camera_closing', event.toJson()), cameraId);
-        await plugin.handleCameraMethodCall(
-            MethodCall('camera_closing', event.toJson()), cameraId);
-        await plugin.handleCameraMethodCall(
-            MethodCall('camera_closing', event.toJson()), cameraId);
+        plugin.hostCameraHandlers[cameraId]!.cameraClosing();
+        plugin.hostCameraHandlers[cameraId]!.cameraClosing();
+        plugin.hostCameraHandlers[cameraId]!.cameraClosing();
 
         // Assert
         expect(await streamQueue.next, event);
@@ -242,14 +239,11 @@
             StreamQueue<CameraErrorEvent>(errorStream);
 
         // Emit test events
-        final CameraErrorEvent event =
-            CameraErrorEvent(cameraId, 'Error Description');
-        await plugin.handleCameraMethodCall(
-            MethodCall('error', event.toJson()), cameraId);
-        await plugin.handleCameraMethodCall(
-            MethodCall('error', event.toJson()), cameraId);
-        await plugin.handleCameraMethodCall(
-            MethodCall('error', event.toJson()), cameraId);
+        const String errorMessage = 'Error Description';
+        final CameraErrorEvent event = CameraErrorEvent(cameraId, errorMessage);
+        plugin.hostCameraHandlers[cameraId]!.error(errorMessage);
+        plugin.hostCameraHandlers[cameraId]!.error(errorMessage);
+        plugin.hostCameraHandlers[cameraId]!.error(errorMessage);
 
         // Assert
         expect(await streamQueue.next, event);
@@ -491,15 +485,6 @@
         expect((widget as Texture).textureId, cameraId);
       });
 
-      test('Should throw UnimplementedError when handling unknown method', () {
-        final CameraWindows plugin = CameraWindows(api: mockApi);
-
-        expect(
-            () => plugin.handleCameraMethodCall(
-                const MethodCall('unknown_method'), 1),
-            throwsA(isA<UnimplementedError>()));
-      });
-
       test('Should get the max zoom level', () async {
         // Act
         final double maxZoomLevel = await plugin.getMaxZoomLevel(cameraId);
diff --git a/packages/camera/camera_windows/test/camera_windows_test.mocks.dart b/packages/camera/camera_windows/test/camera_windows_test.mocks.dart
index d55db4a..7e22e17 100644
--- a/packages/camera/camera_windows/test/camera_windows_test.mocks.dart
+++ b/packages/camera/camera_windows/test/camera_windows_test.mocks.dart
@@ -3,11 +3,11 @@
 // Do not manually edit this file.
 
 // ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'dart:async' as _i3;
+import 'dart:async' as _i4;
 
 import 'package:camera_windows/src/messages.g.dart' as _i2;
 import 'package:mockito/mockito.dart' as _i1;
-import 'package:mockito/src/dummies.dart' as _i4;
+import 'package:mockito/src/dummies.dart' as _i3;
 
 // ignore_for_file: type=lint
 // ignore_for_file: avoid_redundant_argument_values
@@ -37,17 +37,30 @@
 /// See the documentation for Mockito's code generation for more information.
 class MockCameraApi extends _i1.Mock implements _i2.CameraApi {
   @override
-  _i3.Future<List<String?>> getAvailableCameras() => (super.noSuchMethod(
+  String get pigeonVar_messageChannelSuffix => (super.noSuchMethod(
+        Invocation.getter(#pigeonVar_messageChannelSuffix),
+        returnValue: _i3.dummyValue<String>(
+          this,
+          Invocation.getter(#pigeonVar_messageChannelSuffix),
+        ),
+        returnValueForMissingStub: _i3.dummyValue<String>(
+          this,
+          Invocation.getter(#pigeonVar_messageChannelSuffix),
+        ),
+      ) as String);
+
+  @override
+  _i4.Future<List<String>> getAvailableCameras() => (super.noSuchMethod(
         Invocation.method(
           #getAvailableCameras,
           [],
         ),
-        returnValue: _i3.Future<List<String?>>.value(<String?>[]),
-        returnValueForMissingStub: _i3.Future<List<String?>>.value(<String?>[]),
-      ) as _i3.Future<List<String?>>);
+        returnValue: _i4.Future<List<String>>.value(<String>[]),
+        returnValueForMissingStub: _i4.Future<List<String>>.value(<String>[]),
+      ) as _i4.Future<List<String>>);
 
   @override
-  _i3.Future<int> create(
+  _i4.Future<int> create(
     String? cameraName,
     _i2.PlatformMediaSettings? settings,
   ) =>
@@ -59,17 +72,17 @@
             settings,
           ],
         ),
-        returnValue: _i3.Future<int>.value(0),
-        returnValueForMissingStub: _i3.Future<int>.value(0),
-      ) as _i3.Future<int>);
+        returnValue: _i4.Future<int>.value(0),
+        returnValueForMissingStub: _i4.Future<int>.value(0),
+      ) as _i4.Future<int>);
 
   @override
-  _i3.Future<_i2.PlatformSize> initialize(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<_i2.PlatformSize> initialize(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #initialize,
           [cameraId],
         ),
-        returnValue: _i3.Future<_i2.PlatformSize>.value(_FakePlatformSize_0(
+        returnValue: _i4.Future<_i2.PlatformSize>.value(_FakePlatformSize_0(
           this,
           Invocation.method(
             #initialize,
@@ -77,32 +90,32 @@
           ),
         )),
         returnValueForMissingStub:
-            _i3.Future<_i2.PlatformSize>.value(_FakePlatformSize_0(
+            _i4.Future<_i2.PlatformSize>.value(_FakePlatformSize_0(
           this,
           Invocation.method(
             #initialize,
             [cameraId],
           ),
         )),
-      ) as _i3.Future<_i2.PlatformSize>);
+      ) as _i4.Future<_i2.PlatformSize>);
 
   @override
-  _i3.Future<void> dispose(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<void> dispose(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #dispose,
           [cameraId],
         ),
-        returnValue: _i3.Future<void>.value(),
-        returnValueForMissingStub: _i3.Future<void>.value(),
-      ) as _i3.Future<void>);
+        returnValue: _i4.Future<void>.value(),
+        returnValueForMissingStub: _i4.Future<void>.value(),
+      ) as _i4.Future<void>);
 
   @override
-  _i3.Future<String> takePicture(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<String> takePicture(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #takePicture,
           [cameraId],
         ),
-        returnValue: _i3.Future<String>.value(_i4.dummyValue<String>(
+        returnValue: _i4.Future<String>.value(_i3.dummyValue<String>(
           this,
           Invocation.method(
             #takePicture,
@@ -110,32 +123,32 @@
           ),
         )),
         returnValueForMissingStub:
-            _i3.Future<String>.value(_i4.dummyValue<String>(
+            _i4.Future<String>.value(_i3.dummyValue<String>(
           this,
           Invocation.method(
             #takePicture,
             [cameraId],
           ),
         )),
-      ) as _i3.Future<String>);
+      ) as _i4.Future<String>);
 
   @override
-  _i3.Future<void> startVideoRecording(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<void> startVideoRecording(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #startVideoRecording,
           [cameraId],
         ),
-        returnValue: _i3.Future<void>.value(),
-        returnValueForMissingStub: _i3.Future<void>.value(),
-      ) as _i3.Future<void>);
+        returnValue: _i4.Future<void>.value(),
+        returnValueForMissingStub: _i4.Future<void>.value(),
+      ) as _i4.Future<void>);
 
   @override
-  _i3.Future<String> stopVideoRecording(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<String> stopVideoRecording(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #stopVideoRecording,
           [cameraId],
         ),
-        returnValue: _i3.Future<String>.value(_i4.dummyValue<String>(
+        returnValue: _i4.Future<String>.value(_i3.dummyValue<String>(
           this,
           Invocation.method(
             #stopVideoRecording,
@@ -143,32 +156,52 @@
           ),
         )),
         returnValueForMissingStub:
-            _i3.Future<String>.value(_i4.dummyValue<String>(
+            _i4.Future<String>.value(_i3.dummyValue<String>(
           this,
           Invocation.method(
             #stopVideoRecording,
             [cameraId],
           ),
         )),
-      ) as _i3.Future<String>);
+      ) as _i4.Future<String>);
 
   @override
-  _i3.Future<void> pausePreview(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<void> startImageStream(int? cameraId) => (super.noSuchMethod(
+        Invocation.method(
+          #startImageStream,
+          [cameraId],
+        ),
+        returnValue: _i4.Future<void>.value(),
+        returnValueForMissingStub: _i4.Future<void>.value(),
+      ) as _i4.Future<void>);
+
+  @override
+  _i4.Future<void> stopImageStream(int? cameraId) => (super.noSuchMethod(
+        Invocation.method(
+          #stopImageStream,
+          [cameraId],
+        ),
+        returnValue: _i4.Future<void>.value(),
+        returnValueForMissingStub: _i4.Future<void>.value(),
+      ) as _i4.Future<void>);
+
+  @override
+  _i4.Future<void> pausePreview(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #pausePreview,
           [cameraId],
         ),
-        returnValue: _i3.Future<void>.value(),
-        returnValueForMissingStub: _i3.Future<void>.value(),
-      ) as _i3.Future<void>);
+        returnValue: _i4.Future<void>.value(),
+        returnValueForMissingStub: _i4.Future<void>.value(),
+      ) as _i4.Future<void>);
 
   @override
-  _i3.Future<void> resumePreview(int? cameraId) => (super.noSuchMethod(
+  _i4.Future<void> resumePreview(int? cameraId) => (super.noSuchMethod(
         Invocation.method(
           #resumePreview,
           [cameraId],
         ),
-        returnValue: _i3.Future<void>.value(),
-        returnValueForMissingStub: _i3.Future<void>.value(),
-      ) as _i3.Future<void>);
+        returnValue: _i4.Future<void>.value(),
+        returnValueForMissingStub: _i4.Future<void>.value(),
+      ) as _i4.Future<void>);
 }
diff --git a/packages/camera/camera_windows/windows/camera.cpp b/packages/camera/camera_windows/windows/camera.cpp
index 7cfc570..13bf3b3 100644
--- a/packages/camera/camera_windows/windows/camera.cpp
+++ b/packages/camera/camera_windows/windows/camera.cpp
@@ -5,15 +5,6 @@
 #include "camera.h"
 
 namespace camera_windows {
-using flutter::EncodableList;
-using flutter::EncodableMap;
-using flutter::EncodableValue;
-
-// Camera channel events.
-constexpr char kCameraMethodChannelBaseName[] =
-    "plugins.flutter.io/camera_windows/camera";
-constexpr char kCameraClosingEvent[] = "camera_closing";
-constexpr char kErrorEvent[] = "error";
 
 // Camera error codes
 constexpr char kCameraAccessDenied[] = "CameraAccessDenied";
@@ -177,22 +168,16 @@
   pending_results_.clear();
 }
 
-MethodChannel<>* CameraImpl::GetMethodChannel() {
+CameraEventApi* CameraImpl::GetEventApi() {
   assert(messenger_);
   assert(camera_id_);
 
-  // Use existing channel if initialized
-  if (camera_channel_) {
-    return camera_channel_.get();
+  if (!event_api_) {
+    std::string suffix = std::to_string(camera_id_);
+    event_api_ = std::make_unique<CameraEventApi>(messenger_, suffix);
   }
 
-  auto channel_name =
-      std::string(kCameraMethodChannelBaseName) + std::to_string(camera_id_);
-
-  camera_channel_ = std::make_unique<flutter::MethodChannel<>>(
-      messenger_, channel_name, &flutter::StandardMethodCodec::GetInstance());
-
-  return camera_channel_.get();
+  return event_api_.get();
 }
 
 void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) {
@@ -326,12 +311,11 @@
 
 void CameraImpl::OnCaptureError(CameraResult result, const std::string& error) {
   if (messenger_ && camera_id_ >= 0) {
-    auto channel = GetMethodChannel();
-
-    std::unique_ptr<EncodableValue> message_data =
-        std::make_unique<EncodableValue>(EncodableMap(
-            {{EncodableValue("description"), EncodableValue(error)}}));
-    channel->InvokeMethod(kErrorEvent, std::move(message_data));
+    GetEventApi()->Error(
+        error,
+        // TODO(stuartmorgan): Replace with an event channel, since that's how
+        // these calls are used. Given that use case, ignore responses.
+        []() {}, [](const FlutterError& error) {});
   }
 
   std::string error_code = GetErrorCode(result);
@@ -340,9 +324,9 @@
 
 void CameraImpl::OnCameraClosing() {
   if (messenger_ && camera_id_ >= 0) {
-    auto channel = GetMethodChannel();
-    channel->InvokeMethod(kCameraClosingEvent,
-                          std::move(std::make_unique<EncodableValue>()));
+    // TODO(stuartmorgan): Replace with an event channel, since that's how
+    // these calls are used. Given that use case, ignore responses.
+    GetEventApi()->CameraClosing([]() {}, [](const FlutterError& error) {});
   }
 }
 
diff --git a/packages/camera/camera_windows/windows/camera.h b/packages/camera/camera_windows/windows/camera.h
index b4a32d7..2a44ad2 100644
--- a/packages/camera/camera_windows/windows/camera.h
+++ b/packages/camera/camera_windows/windows/camera.h
@@ -5,21 +5,15 @@
 #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_
 #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_
 
-#include <flutter/method_channel.h>
-#include <flutter/standard_method_codec.h>
-
 #include <functional>
 #include <optional>
 #include <variant>
 
 #include "capture_controller.h"
+#include "messages.g.h"
 
 namespace camera_windows {
 
-using flutter::EncodableMap;
-using flutter::MethodChannel;
-using flutter::MethodResult;
-
 // A set of result types that are stored
 // for processing asynchronous commands.
 enum class PendingResultType {
@@ -193,8 +187,8 @@
   // Sends camera closing message to the cameras method channel.
   void OnCameraClosing();
 
-  // Initializes method channel instance and returns pointer it.
-  MethodChannel<>* GetMethodChannel();
+  // Returns the FlutterApi instance used to communicate camera events.
+  CameraEventApi* GetEventApi();
 
   // Finds pending void result by type.
   //
@@ -236,7 +230,7 @@
 
   std::map<PendingResultType, AsyncResult> pending_results_;
   std::unique_ptr<CaptureController> capture_controller_;
-  std::unique_ptr<MethodChannel<>> camera_channel_;
+  std::unique_ptr<CameraEventApi> event_api_;
   flutter::BinaryMessenger* messenger_ = nullptr;
   int64_t camera_id_ = -1;
   std::string device_id_;
diff --git a/packages/camera/camera_windows/windows/messages.g.cpp b/packages/camera/camera_windows/windows/messages.g.cpp
index 08a9194..724d420 100644
--- a/packages/camera/camera_windows/windows/messages.g.cpp
+++ b/packages/camera/camera_windows/windows/messages.g.cpp
@@ -1,7 +1,7 @@
 // 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.
-// Autogenerated from Pigeon (v21.0.0), do not edit directly.
+// Autogenerated from Pigeon (v22.6.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #undef _HAS_EXCEPTIONS
@@ -127,15 +127,16 @@
                                 std::get<bool>(list[4]));
   auto& encodable_frames_per_second = list[1];
   if (!encodable_frames_per_second.IsNull()) {
-    decoded.set_frames_per_second(encodable_frames_per_second.LongValue());
+    decoded.set_frames_per_second(
+        std::get<int64_t>(encodable_frames_per_second));
   }
   auto& encodable_video_bitrate = list[2];
   if (!encodable_video_bitrate.IsNull()) {
-    decoded.set_video_bitrate(encodable_video_bitrate.LongValue());
+    decoded.set_video_bitrate(std::get<int64_t>(encodable_video_bitrate));
   }
   auto& encodable_audio_bitrate = list[3];
   if (!encodable_audio_bitrate.IsNull()) {
-    decoded.set_audio_bitrate(encodable_audio_bitrate.LongValue());
+    decoded.set_audio_bitrate(std::get<int64_t>(encodable_audio_bitrate));
   }
   return decoded;
 }
@@ -166,18 +167,12 @@
   return decoded;
 }
 
-PigeonCodecSerializer::PigeonCodecSerializer() {}
+PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {}
 
-EncodableValue PigeonCodecSerializer::ReadValueOfType(
+EncodableValue PigeonInternalCodecSerializer::ReadValueOfType(
     uint8_t type, flutter::ByteStreamReader* stream) const {
   switch (type) {
-    case 129:
-      return CustomEncodableValue(PlatformMediaSettings::FromEncodableList(
-          std::get<EncodableList>(ReadValue(stream))));
-    case 130:
-      return CustomEncodableValue(PlatformSize::FromEncodableList(
-          std::get<EncodableList>(ReadValue(stream))));
-    case 131: {
+    case 129: {
       const auto& encodable_enum_arg = ReadValue(stream);
       const int64_t enum_arg_value =
           encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue();
@@ -186,17 +181,32 @@
                  : CustomEncodableValue(
                        static_cast<PlatformResolutionPreset>(enum_arg_value));
     }
+    case 130: {
+      return CustomEncodableValue(PlatformMediaSettings::FromEncodableList(
+          std::get<EncodableList>(ReadValue(stream))));
+    }
+    case 131: {
+      return CustomEncodableValue(PlatformSize::FromEncodableList(
+          std::get<EncodableList>(ReadValue(stream))));
+    }
     default:
       return flutter::StandardCodecSerializer::ReadValueOfType(type, stream);
   }
 }
 
-void PigeonCodecSerializer::WriteValue(
+void PigeonInternalCodecSerializer::WriteValue(
     const EncodableValue& value, flutter::ByteStreamWriter* stream) const {
   if (const CustomEncodableValue* custom_value =
           std::get_if<CustomEncodableValue>(&value)) {
-    if (custom_value->type() == typeid(PlatformMediaSettings)) {
+    if (custom_value->type() == typeid(PlatformResolutionPreset)) {
       stream->WriteByte(129);
+      WriteValue(EncodableValue(static_cast<int>(
+                     std::any_cast<PlatformResolutionPreset>(*custom_value))),
+                 stream);
+      return;
+    }
+    if (custom_value->type() == typeid(PlatformMediaSettings)) {
+      stream->WriteByte(130);
       WriteValue(
           EncodableValue(std::any_cast<PlatformMediaSettings>(*custom_value)
                              .ToEncodableList()),
@@ -204,20 +214,13 @@
       return;
     }
     if (custom_value->type() == typeid(PlatformSize)) {
-      stream->WriteByte(130);
+      stream->WriteByte(131);
       WriteValue(
           EncodableValue(
               std::any_cast<PlatformSize>(*custom_value).ToEncodableList()),
           stream);
       return;
     }
-    if (custom_value->type() == typeid(PlatformResolutionPreset)) {
-      stream->WriteByte(131);
-      WriteValue(EncodableValue(static_cast<int>(
-                     std::any_cast<PlatformResolutionPreset>(*custom_value))),
-                 stream);
-      return;
-    }
   }
   flutter::StandardCodecSerializer::WriteValue(value, stream);
 }
@@ -225,7 +228,7 @@
 /// The codec used by CameraApi.
 const flutter::StandardMessageCodec& CameraApi::GetCodec() {
   return flutter::StandardMessageCodec::GetInstance(
-      &PigeonCodecSerializer::GetInstance());
+      &PigeonInternalCodecSerializer::GetInstance());
 }
 
 // Sets up an instance of `CameraApi` to handle messages through the
@@ -653,4 +656,88 @@
                                       error.details()});
 }
 
+// Generated class from Pigeon that represents Flutter messages that can be
+// called from C++.
+CameraEventApi::CameraEventApi(flutter::BinaryMessenger* binary_messenger)
+    : binary_messenger_(binary_messenger), message_channel_suffix_("") {}
+
+CameraEventApi::CameraEventApi(flutter::BinaryMessenger* binary_messenger,
+                               const std::string& message_channel_suffix)
+    : binary_messenger_(binary_messenger),
+      message_channel_suffix_(message_channel_suffix.length() > 0
+                                  ? std::string(".") + message_channel_suffix
+                                  : "") {}
+
+const flutter::StandardMessageCodec& CameraEventApi::GetCodec() {
+  return flutter::StandardMessageCodec::GetInstance(
+      &PigeonInternalCodecSerializer::GetInstance());
+}
+
+void CameraEventApi::CameraClosing(
+    std::function<void(void)>&& on_success,
+    std::function<void(const FlutterError&)>&& on_error) {
+  const std::string channel_name =
+      "dev.flutter.pigeon.camera_windows.CameraEventApi.cameraClosing" +
+      message_channel_suffix_;
+  BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec());
+  EncodableValue encoded_api_arguments = EncodableValue();
+  channel.Send(
+      encoded_api_arguments, [channel_name, on_success = std::move(on_success),
+                              on_error = std::move(on_error)](
+                                 const uint8_t* reply, size_t reply_size) {
+        std::unique_ptr<EncodableValue> response =
+            GetCodec().DecodeMessage(reply, reply_size);
+        const auto& encodable_return_value = *response;
+        const auto* list_return_value =
+            std::get_if<EncodableList>(&encodable_return_value);
+        if (list_return_value) {
+          if (list_return_value->size() > 1) {
+            on_error(
+                FlutterError(std::get<std::string>(list_return_value->at(0)),
+                             std::get<std::string>(list_return_value->at(1)),
+                             list_return_value->at(2)));
+          } else {
+            on_success();
+          }
+        } else {
+          on_error(CreateConnectionError(channel_name));
+        }
+      });
+}
+
+void CameraEventApi::Error(
+    const std::string& error_message_arg,
+    std::function<void(void)>&& on_success,
+    std::function<void(const FlutterError&)>&& on_error) {
+  const std::string channel_name =
+      "dev.flutter.pigeon.camera_windows.CameraEventApi.error" +
+      message_channel_suffix_;
+  BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec());
+  EncodableValue encoded_api_arguments = EncodableValue(EncodableList{
+      EncodableValue(error_message_arg),
+  });
+  channel.Send(
+      encoded_api_arguments, [channel_name, on_success = std::move(on_success),
+                              on_error = std::move(on_error)](
+                                 const uint8_t* reply, size_t reply_size) {
+        std::unique_ptr<EncodableValue> response =
+            GetCodec().DecodeMessage(reply, reply_size);
+        const auto& encodable_return_value = *response;
+        const auto* list_return_value =
+            std::get_if<EncodableList>(&encodable_return_value);
+        if (list_return_value) {
+          if (list_return_value->size() > 1) {
+            on_error(
+                FlutterError(std::get<std::string>(list_return_value->at(0)),
+                             std::get<std::string>(list_return_value->at(1)),
+                             list_return_value->at(2)));
+          } else {
+            on_success();
+          }
+        } else {
+          on_error(CreateConnectionError(channel_name));
+        }
+      });
+}
+
 }  // namespace camera_windows
diff --git a/packages/camera/camera_windows/windows/messages.g.h b/packages/camera/camera_windows/windows/messages.g.h
index e4e42ce..9dcb6fa 100644
--- a/packages/camera/camera_windows/windows/messages.g.h
+++ b/packages/camera/camera_windows/windows/messages.g.h
@@ -1,7 +1,7 @@
 // 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.
-// Autogenerated from Pigeon (v21.0.0), do not edit directly.
+// Autogenerated from Pigeon (v22.6.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #ifndef PIGEON_MESSAGES_G_H_
@@ -52,6 +52,7 @@
 
  private:
   friend class CameraApi;
+  friend class CameraEventApi;
   ErrorOr() = default;
   T TakeValue() && { return std::get<T>(std::move(v_)); }
 
@@ -106,7 +107,8 @@
       const flutter::EncodableList& list);
   flutter::EncodableList ToEncodableList() const;
   friend class CameraApi;
-  friend class PigeonCodecSerializer;
+  friend class CameraEventApi;
+  friend class PigeonInternalCodecSerializer;
   PlatformResolutionPreset resolution_preset_;
   std::optional<int64_t> frames_per_second_;
   std::optional<int64_t> video_bitrate_;
@@ -132,16 +134,17 @@
   static PlatformSize FromEncodableList(const flutter::EncodableList& list);
   flutter::EncodableList ToEncodableList() const;
   friend class CameraApi;
-  friend class PigeonCodecSerializer;
+  friend class CameraEventApi;
+  friend class PigeonInternalCodecSerializer;
   double width_;
   double height_;
 };
 
-class PigeonCodecSerializer : public flutter::StandardCodecSerializer {
+class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer {
  public:
-  PigeonCodecSerializer();
-  inline static PigeonCodecSerializer& GetInstance() {
-    static PigeonCodecSerializer sInstance;
+  PigeonInternalCodecSerializer();
+  inline static PigeonInternalCodecSerializer& GetInstance() {
+    static PigeonInternalCodecSerializer sInstance;
     return sInstance;
   }
 
@@ -216,5 +219,26 @@
  protected:
   CameraApi() = default;
 };
+// Generated class from Pigeon that represents Flutter messages that can be
+// called from C++.
+class CameraEventApi {
+ public:
+  CameraEventApi(flutter::BinaryMessenger* binary_messenger);
+  CameraEventApi(flutter::BinaryMessenger* binary_messenger,
+                 const std::string& message_channel_suffix);
+  static const flutter::StandardMessageCodec& GetCodec();
+  // Called when the camera instance is closing on the native side.
+  void CameraClosing(std::function<void(void)>&& on_success,
+                     std::function<void(const FlutterError&)>&& on_error);
+  // Called when a camera error occurs on the native side.
+  void Error(const std::string& error_message,
+             std::function<void(void)>&& on_success,
+             std::function<void(const FlutterError&)>&& on_error);
+
+ private:
+  flutter::BinaryMessenger* binary_messenger_;
+  std::string message_channel_suffix_;
+};
+
 }  // namespace camera_windows
 #endif  // PIGEON_MESSAGES_G_H_