From 041773750bcf1e1b4187ce482326334bc40378a7 Mon Sep 17 00:00:00 2001 From: ThibaultBee <37510686+ThibaultBee@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:37:46 +0100 Subject: [PATCH] feat(*): add better parameters management. Also fix a mismatch between iOS and Android video resolutions --- .../livestream/{ => utils}/Extensions.kt | 5 +- example/lib/types/resolution.dart | 23 +------- example/lib/types/sample_rate.dart | 14 +---- ios/Classes/FlutterLiveStreamView.swift | 2 +- .../SwiftApiVideoLiveStreamPlugin.swift | 56 +++++++++++-------- ios/apivideo_live_stream.podspec | 2 +- lib/src/types/audio_config.dart | 3 +- lib/src/types/resolution.dart | 48 +++++----------- lib/src/types/sample_rate.dart | 14 +++-- lib/src/types/video_config.dart | 9 ++- 10 files changed, 67 insertions(+), 109 deletions(-) rename android/src/main/kotlin/video/api/flutter/livestream/{ => utils}/Extensions.kt (93%) diff --git a/android/src/main/kotlin/video/api/flutter/livestream/Extensions.kt b/android/src/main/kotlin/video/api/flutter/livestream/utils/Extensions.kt similarity index 93% rename from android/src/main/kotlin/video/api/flutter/livestream/Extensions.kt rename to android/src/main/kotlin/video/api/flutter/livestream/utils/Extensions.kt index 4877735..b307fd4 100644 --- a/android/src/main/kotlin/video/api/flutter/livestream/Extensions.kt +++ b/android/src/main/kotlin/video/api/flutter/livestream/utils/Extensions.kt @@ -31,12 +31,11 @@ fun Map.toAudioConfig(): AudioConfig { fun String.toResolution(): Size { return when (this) { - "240p" -> Size(352, 240) + "240p" -> Size(426, 240) "360p" -> Size(640, 360) - "480p" -> Size(858, 480) + "480p" -> Size(854, 480) "720p" -> Size(1280, 720) "1080p" -> Size(1920, 1080) - "2160p" -> Size(4096, 2160) else -> throw IllegalArgumentException("Unknown resolution: $this") } } diff --git a/example/lib/types/resolution.dart b/example/lib/types/resolution.dart index b40a88d..655beb1 100644 --- a/example/lib/types/resolution.dart +++ b/example/lib/types/resolution.dart @@ -10,27 +10,6 @@ Map getResolutionsMap() { extension ResolutionExtension on Resolution { String toPrettyString() { - var result = ""; - switch (this) { - case Resolution.RESOLUTION_240: - result = "352x240"; - break; - case Resolution.RESOLUTION_360: - result = "640x360"; - break; - case Resolution.RESOLUTION_480: - result = "858x480"; - break; - case Resolution.RESOLUTION_720: - result = "1280x720"; - break; - case Resolution.RESOLUTION_1080: - result = "1920x1080"; - break; - default: - result = "1280x720"; - break; - } - return result; + return "${this.size.width.toInt()}x${this.size.height.toInt()}"; } } diff --git a/example/lib/types/sample_rate.dart b/example/lib/types/sample_rate.dart index 82453bc..ae1a5c5 100644 --- a/example/lib/types/sample_rate.dart +++ b/example/lib/types/sample_rate.dart @@ -10,18 +10,6 @@ Map getSampleRatesMap() { extension SampleRateExtension on SampleRate { String toPrettyString() { - var result = ""; - switch (this) { - case SampleRate.kHz_11: - result = "11 kHz"; - break; - case SampleRate.kHz_22: - result = "22 kHz"; - break; - case SampleRate.kHz_44_1: - result = "44.1 kHz"; - break; - } - return result; + return "${this.value / 1000} KHz"; } } diff --git a/ios/Classes/FlutterLiveStreamView.swift b/ios/Classes/FlutterLiveStreamView.swift index f7878b0..5471ad1 100644 --- a/ios/Classes/FlutterLiveStreamView.swift +++ b/ios/Classes/FlutterLiveStreamView.swift @@ -31,7 +31,7 @@ class FlutterLiveStreamView: NSObject { liveStream.videoConfig } set { - sendEvent(["type": "videoSizeChanged", "width": Double(newValue.resolution.size.width), "height": Double(newValue.resolution.size.height)]) + sendEvent(["type": "videoSizeChanged", "width": Double(newValue.resolution.width), "height": Double(newValue.resolution.height)]) liveStream.videoConfig = newValue } diff --git a/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift b/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift index b815b8f..37a9464 100644 --- a/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift +++ b/ios/Classes/SwiftApiVideoLiveStreamPlugin.swift @@ -54,8 +54,7 @@ public class SwiftApiVideoLiveStreamPlugin: NSObject, FlutterPlugin { result(FlutterError(code: "invalid_parameter", message: "Invalid video config", details: nil)) return } - flutterView.videoConfig = videoParameters.toVideoConfig() - result(nil) + applyVideoConfig(config: videoParameters, flutterView: flutterView, result: result) case "setAudioConfig": guard let flutterView = flutterView else { result(FlutterError(code: "missing_live_stream", message: "Live stream must exist at this point", details: nil)) @@ -65,8 +64,7 @@ public class SwiftApiVideoLiveStreamPlugin: NSObject, FlutterPlugin { result(FlutterError(code: "invalid_parameter", message: "Invalid audio config", details: nil)) return } - flutterView.audioConfig = audioParameters.toAudioConfig() - result(nil) + applyAudioConfig(config: audioParameters, flutterView: flutterView, result: result) case "startPreview": guard let flutterView = flutterView else { result(FlutterError(code: "missing_live_stream", message: "Live stream must exist at this point", details: nil)) @@ -158,40 +156,50 @@ public class SwiftApiVideoLiveStreamPlugin: NSObject, FlutterPlugin { result(FlutterError(code: "missing_live_stream", message: "Live stream must exist at this point", details: nil)) return } - result(["width": flutterView.videoConfig.resolution.size.width, "height": flutterView.videoConfig.resolution.size.height]) + result(["width": flutterView.videoConfig.resolution.width, "height": flutterView.videoConfig.resolution.height]) default: result(FlutterMethodNotImplemented) } } + + private func applyVideoConfig(config: Dictionary, flutterView: FlutterLiveStreamView, result: @escaping FlutterResult) { + let resolutionString = config["resolution"] as! String? + guard let resolutionString else { + result(FlutterError(code: "missing_parameter", message: "Resolution is missing", details: nil)) + return + } + let resolution = resolutionString.toResolution() + guard let resolution else { + result(FlutterError(code: "invalid_parameter", message: "Invalid resolution \(resolutionString)", details: nil)) + return + } + flutterView.videoConfig = VideoConfig(bitrate: config["bitrate"] as! Int, + resolution: resolution.rawValue, + fps: config["fps"] as! Float64) + result(nil) + } + + private func applyAudioConfig(config: Dictionary, flutterView: FlutterLiveStreamView, result: @escaping FlutterResult) { + flutterView.audioConfig = AudioConfig(bitrate: config["bitrate"] as! Int) + result(nil) + } } extension String { - func toResolution() -> Resolution { + func toResolution() -> Resolution? { switch self { case "240p": - return Resolution.RESOLUTION_240 + return Resolution.RESOLUTION_16_9_240P case "360p": - return Resolution.RESOLUTION_360 + return Resolution.RESOLUTION_16_9_360P case "480p": - return Resolution.RESOLUTION_480 + return Resolution.RESOLUTION_16_9_480P case "720p": - return Resolution.RESOLUTION_720 + return Resolution.RESOLUTION_16_9_720P case "1080p": - return Resolution.RESOLUTION_1080 + return Resolution.RESOLUTION_16_9_1080P default: - return Resolution.RESOLUTION_720 + return nil } } } - -extension Dictionary where Key == String { - func toAudioConfig() -> AudioConfig { - return AudioConfig(bitrate: self["bitrate"] as! Int) - } - - func toVideoConfig() -> VideoConfig { - return VideoConfig(bitrate: self["bitrate"] as! Int, - resolution: (self["resolution"] as! String).toResolution(), - fps: self["fps"] as! Float64) - } -} diff --git a/ios/apivideo_live_stream.podspec b/ios/apivideo_live_stream.podspec index e8a3569..bb558bc 100644 --- a/ios/apivideo_live_stream.podspec +++ b/ios/apivideo_live_stream.podspec @@ -15,7 +15,7 @@ A new flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'ApiVideoLiveStream', "1.3.6" + s.dependency 'ApiVideoLiveStream', "1.4.0" s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/src/types/audio_config.dart b/lib/src/types/audio_config.dart index 75c70ec..bbdb153 100644 --- a/lib/src/types/audio_config.dart +++ b/lib/src/types/audio_config.dart @@ -36,7 +36,8 @@ class AudioConfig { this.channel = Channel.stereo, this.sampleRate = SampleRate.kHz_44_1, this.enableEchoCanceler = true, - this.enableNoiseSuppressor = true}); + this.enableNoiseSuppressor = true}) + : assert(bitrate > 0); /// Creates a [AudioConfig] from a [json] map. factory AudioConfig.fromJson(Map json) => diff --git a/lib/src/types/resolution.dart b/lib/src/types/resolution.dart index 9a803ca..9763fc4 100644 --- a/lib/src/types/resolution.dart +++ b/lib/src/types/resolution.dart @@ -1,53 +1,31 @@ +import 'dart:ui'; + import 'package:json_annotation/json_annotation.dart'; /// Enumeration for camera resolution +/// Only 16/9 resolutions are supported. enum Resolution { - /// 352x240 + /// 426x240 @JsonValue("240p") - RESOLUTION_240, + RESOLUTION_240(size: Size(426, 240)), /// 640x360 @JsonValue("360p") - RESOLUTION_360, + RESOLUTION_360(size: Size(640, 360)), - /// 858x480 + /// 854x480 @JsonValue("480p") - RESOLUTION_480, + RESOLUTION_480(size: Size(854, 480)), /// 1280x720 @JsonValue("720p") - RESOLUTION_720, + RESOLUTION_720(size: Size(1280, 720)), /// 1920x1080 @JsonValue("1080p") - RESOLUTION_1080 -} + RESOLUTION_1080(size: Size(1920, 1080)); + + const Resolution({required this.size}); -/// Extension of [Resolution] -extension ResolutionExtension on Resolution { - /// Returns the aspect ratio from a [Resolution] - double getAspectRatio() { - var result = 0.0; - switch (this) { - case Resolution.RESOLUTION_240: - result = 352 / 240; - break; - case Resolution.RESOLUTION_360: - result = 640 / 360; - break; - case Resolution.RESOLUTION_480: - result = 858 / 480; - break; - case Resolution.RESOLUTION_720: - result = 1280 / 720; - break; - case Resolution.RESOLUTION_1080: - result = 1920 / 1080; - break; - default: - result = 16 / 9; - break; - } - return result; - } + final Size size; } diff --git a/lib/src/types/sample_rate.dart b/lib/src/types/sample_rate.dart index d436f27..f39d35e 100644 --- a/lib/src/types/sample_rate.dart +++ b/lib/src/types/sample_rate.dart @@ -1,16 +1,18 @@ import 'package:json_annotation/json_annotation.dart'; /// Enumeration for supported RTMP sample rate +@JsonEnum(valueField: 'value') enum SampleRate { /// 11025 Hz - @JsonValue(11025) - kHz_11, + kHz_11(value: 11025), /// 22050 Hz - @JsonValue(22050) - kHz_22, + kHz_22(value: 22050), /// 44100 Hz - @JsonValue(44100) - kHz_44_1, + kHz_44_1(value: 44100); + + const SampleRate({required this.value}); + + final int value; } diff --git a/lib/src/types/video_config.dart b/lib/src/types/video_config.dart index 8fd22ec..848f6f9 100644 --- a/lib/src/types/video_config.dart +++ b/lib/src/types/video_config.dart @@ -13,19 +13,22 @@ class VideoConfig { /// The live streaming video resolution Resolution resolution; - /// The video framerate in fps + /// The video frame rate in fps int fps; /// Creates a [VideoConfig] instance VideoConfig( {required this.bitrate, this.resolution = Resolution.RESOLUTION_720, - this.fps = 30}); + 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}) - : bitrate = _getDefaultBitrate(resolution); + : assert(fps > 0), + bitrate = _getDefaultBitrate(resolution); /// Creates a [VideoConfig] from a [json] map. factory VideoConfig.fromJson(Map json) =>