删除未使用的相机和记录类来重构记录功能;更新pubspec中的依赖项。Yaml为本地插件路径;简化MainActivity和相关类,以提高性能和可维护性。

This commit is contained in:
2026-07-09 16:17:34 +08:00
parent 75c42ea168
commit 6138eddc16
65 changed files with 3677 additions and 2672 deletions
@@ -0,0 +1,4 @@
export 'src/apivideo_camera_preview.dart';
export 'src/apivideo_live_stream_controller.dart';
export 'src/apivideo_live_stream_mobile_platform.dart';
export 'src/types.dart';
@@ -0,0 +1,183 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:native_device_orientation/native_device_orientation.dart';
import 'apivideo_live_stream_controller.dart';
/// Widget that displays the camera preview of [controller].
///
class ApiVideoCameraPreview extends StatefulWidget {
/// Creates a new [ApiVideoCameraPreview] instance for [controller] and a [child] overlay.
const ApiVideoCameraPreview(
{super.key,
required this.controller,
this.fit = BoxFit.contain,
this.child});
/// The controller for the camera to display the preview for.
final ApiVideoLiveStreamController controller;
/// The [BoxFit] for the video. The [child] is scale to the preview box.
final BoxFit fit;
/// A widget to overlay on top of the camera preview. It is scaled to the camera preview [FittedBox].
final Widget? child;
@override
State<ApiVideoCameraPreview> createState() => _ApiVideoCameraPreviewState();
}
class _ApiVideoCameraPreviewState extends State<ApiVideoCameraPreview> {
_ApiVideoCameraPreviewState() {
_widgetListener = ApiVideoLiveStreamWidgetListener(onTextureReady: () {
final int newTextureId = widget.controller.textureId;
if (newTextureId != _textureId) {
setState(() {
_textureId = newTextureId;
});
}
});
_eventsListener =
ApiVideoLiveStreamEventsListener(onVideoSizeChanged: (size) {
_updateAspectRatio(size);
});
}
late ApiVideoLiveStreamWidgetListener _widgetListener;
late ApiVideoLiveStreamEventsListener _eventsListener;
late int _textureId;
double _aspectRatio = 1.77;
Size _size = const Size(1280, 720);
@override
void initState() {
super.initState();
_textureId = widget.controller.textureId;
widget.controller.addWidgetListener(_widgetListener);
widget.controller.addEventsListener(_eventsListener);
if (widget.controller.isInitialized) {
widget.controller.videoSize.then((size) {
if (size != null) {
_updateAspectRatio(size);
}
});
}
}
@override
void dispose() {
widget.controller.stopPreview();
widget.controller.removeWidgetListener(_widgetListener);
widget.controller.removeEventsListener(_eventsListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return _textureId == ApiVideoLiveStreamController.kUninitializedTextureId
? Container()
: _buildPreview(context);
}
Widget _buildPreview(BuildContext context) {
return NativeDeviceOrientationReader(builder: (context) {
final orientation = NativeDeviceOrientationReader.orientation(context);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(alignment: Alignment.center, children: [
_buildFittedPreview(constraints, orientation),
_buildFittedOverlay(constraints, orientation)
]);
});
});
}
Widget _buildFittedPreview(
BoxConstraints constraints, NativeDeviceOrientation orientation) {
final orientedSize = _size.orientate(orientation);
// See https://github.com/flutter/flutter/issues/17287
return SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: FittedBox(
fit: widget.fit,
clipBehavior: Clip.hardEdge,
child: Center(
child: SizedBox(
width: orientedSize.width,
height: orientedSize.height,
child: _wrapInRotatedBox(
orientation: orientation,
child: widget.controller.buildPreview())))));
}
Widget _buildFittedOverlay(
BoxConstraints constraints, NativeDeviceOrientation orientation) {
final orientedSize = _size.orientate(orientation);
final fittedSize =
applyBoxFit(widget.fit, orientedSize, constraints.biggest);
return SizedBox(
width: fittedSize.destination.width,
height: fittedSize.destination.height,
child: widget.child ?? Container());
}
Widget _wrapInRotatedBox(
{required NativeDeviceOrientation orientation, required Widget child}) {
if (defaultTargetPlatform != TargetPlatform.android) {
return child;
}
return RotatedBox(
quarterTurns: orientation.getQuarterTurns(),
child: child,
);
}
void _updateAspectRatio(Size newSize) async {
final double newAspectRatio = newSize.aspectRatio;
if ((newAspectRatio != _aspectRatio) || (newSize != _size)) {
if (mounted) {
setState(() {
_size = newSize;
_aspectRatio = newAspectRatio;
});
}
}
}
}
extension OrientationHelper on NativeDeviceOrientation {
/// Returns true if the orientation is portrait.
bool isLandscape() {
return [
NativeDeviceOrientation.landscapeLeft,
NativeDeviceOrientation.landscapeRight
].contains(this);
}
/// Returns the number of clockwise quarter turns the orientation is rotated
int getQuarterTurns() {
Map<NativeDeviceOrientation, int> turns = {
NativeDeviceOrientation.unknown: 0,
NativeDeviceOrientation.portraitUp: 0,
NativeDeviceOrientation.landscapeRight: 1,
NativeDeviceOrientation.portraitDown: 2,
NativeDeviceOrientation.landscapeLeft: 3,
};
return turns[this]!;
}
}
extension OrientedSize on Size {
/// Returns the size with width and height swapped if [orientation] is portrait.
Size orientate(NativeDeviceOrientation orientation) {
if (orientation.isLandscape()) {
return Size(width, height);
} else {
return Size(height, width);
}
}
}
@@ -0,0 +1,335 @@
import 'dart:async';
import 'package:apivideo_live_stream/apivideo_live_stream.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'apivideo_live_stream_platform_interface.dart';
import 'types.dart';
ApiVideoLiveStreamPlatform get _platform {
return ApiVideoLiveStreamPlatform.instance;
}
/// Controller of the live streaming
class ApiVideoLiveStreamController {
final VideoConfig _initialVideoConfig;
final AudioConfig _initialAudioConfig;
final CameraPosition _initialCameraPosition;
static const int kUninitializedTextureId = -1;
int _textureId = kUninitializedTextureId;
/// This is just exposed for testing. Do not use it.
@internal
int get textureId => _textureId;
bool _isInitialized = false;
/// Gets the current state of the video player.
bool get isInitialized => _isInitialized;
/// Events
StreamSubscription<dynamic>? _eventSubscription;
List<ApiVideoLiveStreamEventsListener> _eventsListeners = [];
List<ApiVideoLiveStreamWidgetListener> _widgetListeners = [];
/// Creates a new [ApiVideoLiveStreamController] instance.
ApiVideoLiveStreamController(
{required AudioConfig initialAudioConfig,
required VideoConfig initialVideoConfig,
CameraPosition initialCameraPosition = CameraPosition.back,
VoidCallback? onConnectionSuccess,
Function(String)? onConnectionFailed,
VoidCallback? onDisconnection,
Function(Exception)? onError})
: _initialVideoConfig = initialVideoConfig,
_initialAudioConfig = initialAudioConfig,
_initialCameraPosition = initialCameraPosition {
_eventsListeners.add(ApiVideoLiveStreamEventsListener(
onConnectionSuccess: onConnectionSuccess,
onConnectionFailed: onConnectionFailed,
onDisconnection: onDisconnection,
onError: onError));
}
ApiVideoLiveStreamController.fromListener(
{required AudioConfig initialAudioConfig,
required VideoConfig initialVideoConfig,
CameraPosition initialCameraPosition = CameraPosition.back,
ApiVideoLiveStreamEventsListener? listener})
: _initialVideoConfig = initialVideoConfig,
_initialAudioConfig = initialAudioConfig,
_initialCameraPosition = initialCameraPosition {
if (listener != null) {
_eventsListeners.add(listener);
}
}
/// Creates a new live stream instance with initial audio and video configurations.
Future<void> initialize() async {
_textureId = await _platform.initialize() ?? kUninitializedTextureId;
_eventSubscription = _platform
.liveStreamingEventsFor(_textureId)
.listen(_eventListener, onError: _errorListener);
for (var listener in [..._widgetListeners]) {
if (listener.onTextureReady != null) {
listener.onTextureReady!();
}
}
await setCameraPosition(_initialCameraPosition);
await setVideoConfig(_initialVideoConfig);
await setAudioConfig(_initialAudioConfig);
await startPreview();
_isInitialized = true;
return;
}
/// Disposes the live stream instance.
Future<void> dispose() async {
await _eventSubscription?.cancel();
_eventsListeners.clear();
_widgetListeners.clear();
await _platform.dispose();
return;
}
/// Sets new video parameters.
///
/// Do not call when in live or when preview is running.
Future<void> setVideoConfig(VideoConfig videoConfig) {
return _platform.setVideoConfig(videoConfig);
}
/// Sets new audio parameters.
///
/// Do not call when in live or when preview is running.
Future<void> setAudioConfig(AudioConfig audioConfig) {
return _platform.setAudioConfig(audioConfig);
}
/// Starts the live stream to the specified "[url]/[streamKey]".
Future<void> startStreaming(
{required String streamKey,
String url = "rtmp://broadcast.api.video/s/"}) async {
return _platform.startStreaming(streamKey: streamKey, url: url);
}
/// Stops the live stream.
Future<void> stopStreaming() {
return _platform.stopStreaming();
}
/// Starts the camera preview.
///
/// The purpose of this method is to be called when application is sent
/// to foreground.
Future<void> startPreview() {
return _platform.startPreview();
}
/// Stops the camera preview.
///
/// The purpose of this method is to be called when application is sent
/// to background.
Future<void> stopPreview() {
return _platform.stopPreview();
}
/// Same as [stopStreaming] and [stopPreview]
Future<void> stop() async {
await stopStreaming();
await stopPreview();
}
/// Gets if live stream is streaming or not.
Future<bool> get isStreaming {
return _platform.getIsStreaming();
}
/// Changes current back/front camera to front/back camera
Future<void> switchCamera() async {
final cameraPosition = await this.cameraPosition;
if (cameraPosition == CameraPosition.back) {
return setCameraPosition(CameraPosition.front);
} else {
return setCameraPosition(CameraPosition.back);
}
}
/// Gets the current camera position
Future<CameraPosition> get cameraPosition {
return _platform.getCameraPosition();
}
/// Sets the current camera position
Future<void> setCameraPosition(CameraPosition position) {
return _platform.setCameraPosition(position);
}
/// Lists back cameras exposed by Android CameraManager.
Future<List<ApiVideoBackCamera>> getBackCameras() async {
final cameras = await _platform.getBackCameras();
return cameras
.map(ApiVideoBackCamera.fromJson)
.where((camera) => camera.cameraId.isNotEmpty)
.toList(growable: false);
}
/// Selects a concrete camera id. Android-only extension.
Future<void> setCameraId(String cameraId) {
return _platform.setCameraId(cameraId);
}
/// Toggle mutes/unmutes from the microphone. See [isMuted] and [setIsMuted].
Future<void> toggleMute() async {
final isMuted = await this.isMuted;
await setIsMuted(!isMuted);
}
/// Gets if live stream is muted or not.
Future<bool> get isMuted {
return _platform.getIsMuted();
}
/// Mutes/unmutes the microphone.
Future<void> setIsMuted(bool isMuted) {
return _platform.setIsMuted(isMuted);
}
Future<Size?> get videoSize {
return _platform.getVideoSize();
}
/// Builds the preview widget.
@internal
Widget buildPreview() {
return Texture(textureId: textureId);
}
void addEventsListener(ApiVideoLiveStreamEventsListener listener) {
_eventsListeners.add(listener);
}
void removeEventsListener(ApiVideoLiveStreamEventsListener listener) {
_eventsListeners.remove(listener);
}
/// This is exposed for internal use only. Do not use it.
@internal
void addWidgetListener(ApiVideoLiveStreamWidgetListener listener) {
_widgetListeners.add(listener);
}
/// This is exposed for internal use only. Do not use it.
@internal
void removeWidgetListener(ApiVideoLiveStreamWidgetListener listener) {
_widgetListeners.remove(listener);
}
void _errorListener(Object obj) {
final PlatformException e = obj as PlatformException;
for (var listener in [..._eventsListeners]) {
if (listener.onError != null) {
listener.onError!(e);
}
}
}
void _eventListener(LiveStreamingEvent event) {
switch (event.type) {
case LiveStreamingEventType.connected:
for (var listener in [..._eventsListeners]) {
if (listener.onConnectionSuccess != null) {
listener.onConnectionSuccess!();
}
}
break;
case LiveStreamingEventType.disconnected:
for (var listener in [..._eventsListeners]) {
if (listener.onDisconnection != null) {
listener.onDisconnection!();
}
}
break;
case LiveStreamingEventType.connectionFailed:
for (var listener in [..._eventsListeners]) {
if (listener.onConnectionFailed != null) {
listener.onConnectionFailed!(event.data as String);
}
}
break;
case LiveStreamingEventType.videoSizeChanged:
for (var listener in [..._eventsListeners]) {
if (listener.onVideoSizeChanged != null) {
listener.onVideoSizeChanged!(event.data as Size);
}
}
break;
case LiveStreamingEventType.unknown:
// Nothing to do
break;
}
}
}
class ApiVideoBackCamera {
const ApiVideoBackCamera({
required this.cameraId,
required this.minFocalLength,
required this.sensorWidth,
required this.sensorHeight,
required this.horizontalFov,
});
final String cameraId;
final double minFocalLength;
final double sensorWidth;
final double sensorHeight;
final double horizontalFov;
factory ApiVideoBackCamera.fromJson(Map<String, dynamic> json) {
return ApiVideoBackCamera(
cameraId: json['cameraId'] as String? ?? '',
minFocalLength: (json['minFocalLength'] as num?)?.toDouble() ?? 0,
sensorWidth: (json['sensorWidth'] as num?)?.toDouble() ?? 0,
sensorHeight: (json['sensorHeight'] as num?)?.toDouble() ?? 0,
horizontalFov: (json['horizontalFov'] as num?)?.toDouble() ?? 0,
);
}
}
class ApiVideoLiveStreamEventsListener {
/// Gets notified when the connection is successful
final VoidCallback? onConnectionSuccess;
/// Gets notified when the connection failed
final Function(String)? onConnectionFailed;
/// Gets notified when the device has been disconnected
final VoidCallback? onDisconnection;
/// Gets notified when the video size has changed. Mostly designed to update Widget aspect ratio.
final Function(Size)? onVideoSizeChanged;
/// Gets notified when an error occurs
final Function(Exception)? onError;
ApiVideoLiveStreamEventsListener(
{this.onConnectionSuccess,
this.onConnectionFailed,
this.onDisconnection,
this.onVideoSizeChanged,
this.onError});
}
class ApiVideoLiveStreamWidgetListener {
final VoidCallback? onTextureReady;
ApiVideoLiveStreamWidgetListener({this.onTextureReady});
}
@@ -0,0 +1,155 @@
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'apivideo_live_stream_platform_interface.dart';
import 'types.dart';
/// Controller of the live streaming
class ApiVideoMobileLiveStreamPlatform extends ApiVideoLiveStreamPlatform {
final MethodChannel _channel =
const MethodChannel('video.api.livestream/controller');
/// Registers this class as the default instance of [PathProviderPlatform].
static void registerWith() {
ApiVideoLiveStreamPlatform.instance = ApiVideoMobileLiveStreamPlatform();
}
@override
Future<int?> initialize() async {
final Map<String, dynamic>? reply =
await _channel.invokeMapMethod<String, dynamic>('create');
return reply!['textureId']! as int;
}
@override
Future<void> dispose() {
return _channel.invokeMapMethod<String, dynamic>('dispose');
}
@override
Future<void> setVideoConfig(VideoConfig videoConfig) {
return _channel.invokeMethod('setVideoConfig', videoConfig.toJson());
}
@override
Future<void> setAudioConfig(AudioConfig audioConfig) {
return _channel.invokeMethod('setAudioConfig', audioConfig.toJson());
}
@override
Future<void> startStreaming(
{required String streamKey, required String url}) {
return _channel.invokeMethod('startStreaming', <String, dynamic>{
'streamKey': streamKey,
'url': url,
});
}
@override
Future<void> stopStreaming() {
return _channel.invokeMethod('stopStreaming');
}
@override
Future<void> startPreview() {
return _channel.invokeMethod('startPreview');
}
@override
Future<void> stopPreview() {
return _channel.invokeMethod('stopPreview');
}
@override
Future<bool> getIsStreaming() async {
final Map<dynamic, dynamic> reply =
await _channel.invokeMethod('getIsStreaming') as Map;
return reply['isStreaming'] as bool;
}
@override
Future<void> setCameraPosition(CameraPosition cameraPosition) {
return _channel.invokeMethod('setCameraPosition',
<String, dynamic>{'position': cameraPosition.toJson()});
}
@override
Future<List<Map<String, dynamic>>> getBackCameras() async {
final List<dynamic> reply =
await _channel.invokeMethod('getBackCameras') as List<dynamic>;
return reply
.whereType<Map<dynamic, dynamic>>()
.map((item) => Map<String, dynamic>.from(item))
.toList(growable: false);
}
@override
Future<void> setCameraId(String cameraId) {
return _channel.invokeMethod(
'setCameraId',
<String, dynamic>{'cameraId': cameraId},
);
}
@override
Future<CameraPosition> getCameraPosition() async {
final Map<dynamic, dynamic> reply =
await _channel.invokeMethod('getCameraPosition') as Map;
return CameraPosition.fromJson(reply['position'] as String);
}
@override
Future<void> setIsMuted(bool isMuted) {
return _channel
.invokeMethod('setIsMuted', <String, dynamic>{'isMuted': isMuted});
}
@override
Future<bool> getIsMuted() async {
final Map<dynamic, dynamic> reply =
await _channel.invokeMethod('getIsMuted') as Map;
return reply['isMuted'] as bool;
}
@override
Future<Size?> getVideoSize() async {
final Map<dynamic, dynamic> reply =
await _channel.invokeMethod('getVideoSize') as Map;
if (reply.containsKey("width") && reply.containsKey("height")) {
return Size(reply["width"] as double, reply["height"] as double);
} else {
return null;
}
}
/// Builds the preview widget.
@override
Widget buildPreview(int textureId) {
return Texture(textureId: textureId);
}
@override
Stream<LiveStreamingEvent> liveStreamingEventsFor(int textureId) {
return EventChannel('video.api.livestream/events')
.receiveBroadcastStream()
.map((dynamic map) {
final Map<dynamic, dynamic> event = map as Map<dynamic, dynamic>;
switch (event['type']) {
case 'connected':
return LiveStreamingEvent(type: LiveStreamingEventType.connected);
case 'disconnected':
return LiveStreamingEvent(type: LiveStreamingEventType.disconnected);
case 'connectionFailed':
return LiveStreamingEvent(
type: LiveStreamingEventType.connectionFailed,
data: event['message']);
case 'videoSizeChanged':
return LiveStreamingEvent(
type: LiveStreamingEventType.videoSizeChanged,
data: Size(event['width'] as double, event['height'] as double));
default:
return LiveStreamingEvent(type: LiveStreamingEventType.unknown);
}
});
}
}
@@ -0,0 +1,132 @@
import 'package:flutter/widgets.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'types.dart';
abstract class ApiVideoLiveStreamPlatform extends PlatformInterface {
/// Constructs a ApiVideoPlayerPlatform.
ApiVideoLiveStreamPlatform() : super(token: _token);
static final Object _token = Object();
static ApiVideoLiveStreamPlatform _instance = _PlatformImplementation();
/// The default instance of [ApiVideoLiveStreamPlatform] to use.
///
/// Defaults to [_PlatformImplementation].
static ApiVideoLiveStreamPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [ApiVideoLiveStreamPlatform] when
/// they register themselves.
static set instance(ApiVideoLiveStreamPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// Creates a new live stream instance
Future<int?> initialize() {
throw UnimplementedError('initialize() has not been implemented.');
}
/// Disposes the live stream instance
Future<void> dispose() {
throw UnimplementedError('dispose() has not been implemented.');
}
Future<void> setVideoConfig(VideoConfig videoConfig) {
throw UnimplementedError('setVideoConfig() has not been implemented.');
}
Future<void> setAudioConfig(AudioConfig audioConfig) {
throw UnimplementedError('setAudioConfig() has not been implemented.');
}
Future<void> startStreaming(
{required String streamKey, required String url}) {
throw UnimplementedError('startStreaming() has not been implemented.');
}
Future<void> stopStreaming() {
throw UnimplementedError('stopStreaming() has not been implemented.');
}
Future<void> startPreview() {
throw UnimplementedError('startPreview() has not been implemented.');
}
Future<void> stopPreview() {
throw UnimplementedError('stopPreview() has not been implemented.');
}
Future<bool> getIsStreaming() {
throw UnimplementedError('getIsStreaming() has not been implemented.');
}
Future<CameraPosition> getCameraPosition() {
throw UnimplementedError('getCameraPosition() has not been implemented.');
}
Future<void> setCameraPosition(CameraPosition cameraPosition) {
throw UnimplementedError('setCameraPosition() has not been implemented.');
}
Future<List<Map<String, dynamic>>> getBackCameras() {
throw UnimplementedError('getBackCameras() has not been implemented.');
}
Future<void> setCameraId(String cameraId) {
throw UnimplementedError('setCameraId() has not been implemented.');
}
Future<bool> getIsMuted() {
throw UnimplementedError('getIsMuted() has not been implemented.');
}
Future<void> setIsMuted(bool isMuted) {
throw UnimplementedError('setIsMuted() has not been implemented.');
}
Future<Size?> getVideoSize() {
throw UnimplementedError('getVideoSize() has not been implemented.');
}
/// Returns a Stream of [LiveStreamingEvent]s.
Stream<LiveStreamingEvent> liveStreamingEventsFor(int textureId) {
throw UnimplementedError(
'liveStreamingEventsFor() has not been implemented.');
}
Widget buildPreview(int textureId) {
throw UnimplementedError('buildPreview() has not been implemented.');
}
}
class _PlatformImplementation extends ApiVideoLiveStreamPlatform {}
class LiveStreamingEvent {
/// Adds optional parameters here if needed
final Object? data;
/// The [LiveStreamingEventType]
final LiveStreamingEventType type;
LiveStreamingEvent({required this.type, this.data});
}
enum LiveStreamingEventType {
/// The live streaming is connected.
connected,
/// The live streaming has just been disconnected.
disconnected,
/// The connection to the server failed.
connectionFailed,
/// The video size has changed.
videoSizeChanged,
/// Unknown event
unknown
}
@@ -0,0 +1,6 @@
export 'types/audio_config.dart';
export 'types/camera_position.dart';
export 'types/channel.dart';
export 'types/resolution.dart';
export 'types/sample_rate.dart';
export 'types/video_config.dart';
@@ -0,0 +1,48 @@
import 'package:json_annotation/json_annotation.dart';
import 'channel.dart';
import 'sample_rate.dart';
part 'audio_config.g.dart';
/// Live streaming audio configuration.
@JsonSerializable()
class AudioConfig {
/// The video bitrate in bps
int bitrate;
/// The number of audio channels
/// Only available on Android
Channel channel;
/// The sample rate of the audio capture
/// Only available on Android
SampleRate sampleRate;
/// Enable the echo cancellation
/// Only available on Android
bool enableEchoCanceler;
/// Enable the noise suppressor
/// Only available on Android
bool enableNoiseSuppressor;
/// Creates a new [AudioConfig] instance.
///
/// [sampleRate] is only supported on Android.
/// [channel] is only supported on Android.
AudioConfig(
{this.bitrate = 128000,
this.channel = Channel.stereo,
this.sampleRate = SampleRate.kHz_44_1,
this.enableEchoCanceler = true,
this.enableNoiseSuppressor = true})
: assert(bitrate > 0);
/// Creates a [AudioConfig] from a [json] map.
factory AudioConfig.fromJson(Map<String, dynamic> json) =>
_$AudioConfigFromJson(json);
/// Creates a json map from a [AudioConfig].
Map<String, dynamic> toJson() => _$AudioConfigToJson(this);
}
@@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'audio_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AudioConfig _$AudioConfigFromJson(Map<String, dynamic> json) => AudioConfig(
bitrate: json['bitrate'] as int? ?? 128000,
channel: $enumDecodeNullable(_$ChannelEnumMap, json['channel']) ??
Channel.stereo,
sampleRate:
$enumDecodeNullable(_$SampleRateEnumMap, json['sampleRate']) ??
SampleRate.kHz_44_1,
enableEchoCanceler: json['enableEchoCanceler'] as bool? ?? true,
enableNoiseSuppressor: json['enableNoiseSuppressor'] as bool? ?? true,
);
Map<String, dynamic> _$AudioConfigToJson(AudioConfig instance) =>
<String, dynamic>{
'bitrate': instance.bitrate,
'channel': _$ChannelEnumMap[instance.channel]!,
'sampleRate': _$SampleRateEnumMap[instance.sampleRate]!,
'enableEchoCanceler': instance.enableEchoCanceler,
'enableNoiseSuppressor': instance.enableNoiseSuppressor,
};
const _$ChannelEnumMap = {
Channel.stereo: 'stereo',
Channel.mono: 'mono',
};
const _$SampleRateEnumMap = {
SampleRate.kHz_11: 11025,
SampleRate.kHz_22: 22050,
SampleRate.kHz_44_1: 44100,
};
@@ -0,0 +1,15 @@
/// Camera facing direction
enum CameraPosition {
/// Front camera
front,
/// Back camera
back,
/// Other camera (external for example)
other;
String toJson() => name;
static CameraPosition fromJson(String json) => values.byName(json);
}
@@ -0,0 +1,12 @@
import 'package:json_annotation/json_annotation.dart';
/// Audio channel
enum Channel {
/// Stereo (2 channels)
@JsonValue("stereo")
stereo,
/// Mono (1 channel)
@JsonValue("mono")
mono,
}
@@ -0,0 +1,31 @@
import 'dart:ui';
import 'package:json_annotation/json_annotation.dart';
/// Enumeration for camera resolution
/// Only 16/9 resolutions are supported.
enum Resolution {
/// 426x240
@JsonValue("240p")
RESOLUTION_240(size: Size(426, 240)),
/// 640x360
@JsonValue("360p")
RESOLUTION_360(size: Size(640, 360)),
/// 854x480
@JsonValue("480p")
RESOLUTION_480(size: Size(854, 480)),
/// 1280x720
@JsonValue("720p")
RESOLUTION_720(size: Size(1280, 720)),
/// 1920x1080
@JsonValue("1080p")
RESOLUTION_1080(size: Size(1920, 1080));
const Resolution({required this.size});
final Size size;
}
@@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
/// Enumeration for supported RTMP sample rate
@JsonEnum(valueField: 'value')
enum SampleRate {
/// 11025 Hz
kHz_11(value: 11025),
/// 22050 Hz
kHz_22(value: 22050),
/// 44100 Hz
kHz_44_1(value: 44100);
const SampleRate({required this.value});
final int value;
}
@@ -0,0 +1,55 @@
import 'package:json_annotation/json_annotation.dart';
import 'resolution.dart';
part 'video_config.g.dart';
/// Live streaming video configuration.
@JsonSerializable()
class VideoConfig {
/// The video bitrate in bps
int bitrate;
/// The live streaming video resolution
Resolution resolution;
/// The video frame rate in fps
int fps;
/// Creates a [VideoConfig] instance
VideoConfig(
{required this.bitrate,
this.resolution = Resolution.RESOLUTION_720,
this.fps = 30})
: assert(bitrate > 0),
assert(fps > 0);
/// Creates a [VideoConfig] instance where bitrate is set according to the given [resolution].
VideoConfig.withDefaultBitrate(
{this.resolution = Resolution.RESOLUTION_720, this.fps = 30})
: assert(fps > 0),
bitrate = _getDefaultBitrate(resolution);
/// Creates a [VideoConfig] from a [json] map.
factory VideoConfig.fromJson(Map<String, dynamic> json) =>
_$VideoConfigFromJson(json);
/// Creates a json map from a [VideoConfig].
Map<String, dynamic> toJson() => _$VideoConfigToJson(this);
/// Returns the default bitrate for the given [resolution].
static int _getDefaultBitrate(Resolution resolution) {
switch (resolution) {
case Resolution.RESOLUTION_240:
return 800000;
case Resolution.RESOLUTION_360:
return 1000000;
case Resolution.RESOLUTION_480:
return 1300000;
case Resolution.RESOLUTION_720:
return 2000000;
case Resolution.RESOLUTION_1080:
return 3500000;
}
}
}
@@ -0,0 +1,30 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'video_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
VideoConfig _$VideoConfigFromJson(Map<String, dynamic> json) => VideoConfig(
bitrate: json['bitrate'] as int,
resolution:
$enumDecodeNullable(_$ResolutionEnumMap, json['resolution']) ??
Resolution.RESOLUTION_720,
fps: json['fps'] as int? ?? 30,
);
Map<String, dynamic> _$VideoConfigToJson(VideoConfig instance) =>
<String, dynamic>{
'bitrate': instance.bitrate,
'resolution': _$ResolutionEnumMap[instance.resolution]!,
'fps': instance.fps,
};
const _$ResolutionEnumMap = {
Resolution.RESOLUTION_240: '240p',
Resolution.RESOLUTION_360: '360p',
Resolution.RESOLUTION_480: '480p',
Resolution.RESOLUTION_720: '720p',
Resolution.RESOLUTION_1080: '1080p',
};