diff --git a/lib/src/participant/local_participant.dart b/lib/src/participant/local_participant.dart index 249e7d3..ddae576 100644 --- a/lib/src/participant/local_participant.dart +++ b/lib/src/participant/local_participant.dart @@ -101,8 +101,7 @@ class LocalParticipant extends Participant { publishOptions ?? room.roomOptions?.defaultVideoPublishOptions; // use constraints passed to getUserMedia by default - int width = track.currentOptions.params.width; - int height = track.currentOptions.params.height; + VideoDimensions dimensions = track.currentOptions.params.dimensions; if (kIsWeb) { // getSettings() is only implemented for Web @@ -110,10 +109,10 @@ class LocalParticipant extends Participant { // try to use getSettings for more accurate resolution final settings = track.mediaStreamTrack.getSettings(); if (settings['width'] is int) { - width = settings['width'] as int; + dimensions = dimensions.copyWith(width: settings['width'] as int); } if (settings['height'] is int) { - height = settings['height'] as int; + dimensions = dimensions.copyWith(height: settings['height'] as int); } } catch (_) { logger.warning('Failed to call `mediaStreamTrack.getSettings()`'); @@ -125,7 +124,7 @@ class LocalParticipant extends Participant { name: track.name, kind: track.kind, source: track.source.toPBType(), - dimension: TrackDimension(width, height), + dimensions: dimensions, ); logger.fine('publishVideoTrack addTrack response: ${trackInfo}'); @@ -133,12 +132,11 @@ class LocalParticipant extends Participant { await track.start(); logger.fine( - 'Compute encodings with resolution: ${width}x${height}, options: ${publishOptions}'); + 'Compute encodings with resolution: ${dimensions}, options: ${publishOptions}'); // Video encodings and simulcasts final encodings = Utils.computeVideoEncodings( - width: width, - height: height, + dimensions: dimensions, options: publishOptions, ); diff --git a/lib/src/publication/track_publication.dart b/lib/src/publication/track_publication.dart index 1f3fefa..3506966 100644 --- a/lib/src/publication/track_publication.dart +++ b/lib/src/publication/track_publication.dart @@ -32,7 +32,7 @@ abstract class TrackPublication extends Disposable { bool get muted => track?.muted ?? false; bool simulcasted = false; - TrackDimension? dimension; + VideoDimensions? dimension; bool get subscribed => track != null; @@ -52,7 +52,7 @@ abstract class TrackPublication extends Disposable { void updateFromInfo(lk_models.TrackInfo info) { simulcasted = info.simulcast; if (info.type == lk_models.TrackType.VIDEO) { - dimension = TrackDimension(info.width, info.height); + dimension = VideoDimensions(info.width, info.height); } } diff --git a/lib/src/rtc_engine.dart b/lib/src/rtc_engine.dart index 6cee6c2..936338b 100644 --- a/lib/src/rtc_engine.dart +++ b/lib/src/rtc_engine.dart @@ -147,7 +147,7 @@ class RTCEngine extends Disposable with EventsEmittable { required String name, required lk_models.TrackType kind, required lk_models.TrackSource source, - TrackDimension? dimension, + VideoDimensions? dimensions, bool? dtx, }) async { // TODO: Check if cid already published @@ -158,7 +158,7 @@ class RTCEngine extends Disposable with EventsEmittable { name: name, type: kind, source: source, - dimension: dimension, + dimensions: dimensions, dtx: dtx, ); diff --git a/lib/src/signal_client.dart b/lib/src/signal_client.dart index d4b6d5f..007b506 100644 --- a/lib/src/signal_client.dart +++ b/lib/src/signal_client.dart @@ -148,7 +148,7 @@ class SignalClient extends Disposable with EventsEmittable { required String name, required lk_models.TrackType type, required lk_models.TrackSource source, - TrackDimension? dimension, + VideoDimensions? dimensions, bool? dtx, }) { final req = lk_rtc.AddTrackRequest( @@ -158,10 +158,10 @@ class SignalClient extends Disposable with EventsEmittable { source: source, ); - if (type == lk_models.TrackType.VIDEO && dimension != null) { + if (type == lk_models.TrackType.VIDEO && dimensions != null) { // video specific - req.width = dimension.width; - req.height = dimension.height; + req.width = dimensions.width; + req.height = dimensions.height; } if (type == lk_models.TrackType.AUDIO && dtx != null) { diff --git a/lib/src/track/options.dart b/lib/src/track/options.dart index c10ce05..21b7d73 100644 --- a/lib/src/track/options.dart +++ b/lib/src/track/options.dart @@ -1,6 +1,7 @@ import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import '../track/local/video.dart'; import '../track/local/audio.dart'; +import '../types.dart'; /// A type that represents front or back of the camera. enum CameraPosition { @@ -112,14 +113,12 @@ extension VideoEncodingExt on VideoEncoding { class VideoParameters { final String description; - final int width; - final int height; + final VideoDimensions dimensions; final VideoEncoding encoding; const VideoParameters({ required this.description, - required this.width, - required this.height, + required this.dimensions, required this.encoding, }); @@ -129,8 +128,7 @@ class VideoParameters { static const presetQVGA169 = VideoParameters( description: 'QVGA(320x180) 16:9', - width: 320, - height: 180, + dimensions: VideoDimensions(320, 180), encoding: VideoEncoding( maxBitrate: 125000, maxFramerate: 15, @@ -139,8 +137,7 @@ class VideoParameters { static const presetVGA169 = VideoParameters( description: 'VGA(640x360) 16:9', - width: 640, - height: 360, + dimensions: VideoDimensions(640, 360), encoding: VideoEncoding( maxBitrate: 400000, maxFramerate: 30, @@ -149,8 +146,7 @@ class VideoParameters { static const presetQHD169 = VideoParameters( description: 'QHD(960x540) 16:9', - width: 960, - height: 540, + dimensions: VideoDimensions(960, 540), encoding: VideoEncoding( maxBitrate: 800000, maxFramerate: 30, @@ -159,8 +155,7 @@ class VideoParameters { static const presetHD169 = VideoParameters( description: 'HD(1280x720) 16:9', - width: 1280, - height: 720, + dimensions: VideoDimensions(1280, 720), encoding: VideoEncoding( maxBitrate: 2500000, maxFramerate: 30, @@ -169,8 +164,7 @@ class VideoParameters { static const presetFHD169 = VideoParameters( description: 'FHD(1920x1080) 16:9', - width: 1920, - height: 1080, + dimensions: VideoDimensions(1920, 1080), encoding: VideoEncoding( maxBitrate: 4000000, maxFramerate: 30, @@ -179,8 +173,7 @@ class VideoParameters { static const presetQVGA43 = VideoParameters( description: 'QVGA(240x180) 4:3', - width: 240, - height: 180, + dimensions: VideoDimensions(240, 180), encoding: VideoEncoding( maxBitrate: 100000, maxFramerate: 15, @@ -189,8 +182,7 @@ class VideoParameters { static const presetVGA43 = VideoParameters( description: 'VGA(480x360) 4:3', - width: 480, - height: 360, + dimensions: VideoDimensions(480, 360), encoding: VideoEncoding( maxBitrate: 320000, maxFramerate: 30, @@ -199,8 +191,7 @@ class VideoParameters { static const presetQHD43 = VideoParameters( description: 'QHD(720x540) 4:3', - width: 720, - height: 540, + dimensions: VideoDimensions(720, 540), encoding: VideoEncoding( maxBitrate: 640000, maxFramerate: 30, @@ -209,8 +200,7 @@ class VideoParameters { static const presetHD43 = VideoParameters( description: 'HD(960x720) 4:3', - width: 960, - height: 720, + dimensions: VideoDimensions(960, 720), encoding: VideoEncoding( maxBitrate: 2000000, maxFramerate: 30, @@ -219,8 +209,7 @@ class VideoParameters { static const presetFHD43 = VideoParameters( description: 'FHD(1440x1080) 4:3', - width: 1440, - height: 1080, + dimensions: VideoDimensions(1440, 1080), encoding: VideoEncoding( maxBitrate: 3200000, maxFramerate: 30, @@ -248,8 +237,8 @@ class VideoParameters { // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia // Map toMediaConstraintsMap() => { - 'width': width, - 'height': height, + 'width': dimensions.width, + 'height': dimensions.height, 'frameRate': encoding.maxFramerate, }; } diff --git a/lib/src/types.dart b/lib/src/types.dart index b779940..7f362d2 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'extensions.dart'; +import 'dart:math' as math; typedef CancelListenFunc = Function(); @@ -134,12 +135,27 @@ class RTCIceServer { } @immutable -class TrackDimension { +class VideoDimensions { final int width; final int height; - const TrackDimension( + const VideoDimensions( this.width, this.height, ); + + @override + String toString() => 'VideoDimensions(${width}×${height})'; + + /// Returns the larger value + int max() => math.max(width, height); + + VideoDimensions copyWith({ + int? width, + int? height, + }) => + VideoDimensions( + width ?? this.width, + height ?? this.height, + ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 8f21dfc..d36a6a5 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -6,6 +6,7 @@ import 'extensions.dart'; import 'livekit.dart'; import 'options.dart'; import 'track/options.dart'; +import 'types.dart'; extension UriExt on Uri { bool get isSecureScheme => ['https', 'wss'].contains(scheme); @@ -54,53 +55,49 @@ class Utils { ); } - static List _presetsForResolution( - int width, - int height, + static List _presetsForDimensions( + VideoDimensions dimensions, ) { - final double aspect = width / height; + final double aspect = dimensions.width / dimensions.height; if ((aspect - 16.0 / 9.0).abs() < (aspect - 4.0 / 3.0).abs()) { return VideoParameters.presets169; } return VideoParameters.presets43; } - static VideoParameters _findPresetForResolution( - int width, - int height, { + static VideoParameters _findPresetForDimensions( + VideoDimensions dimensions, { required List presets, }) { assert(presets.isNotEmpty, 'presets should not be empty'); VideoParameters result = presets.first; for (final preset in presets) { - if (width >= preset.width && height >= preset.height) result = preset; + if (dimensions.width >= preset.dimensions.width && + dimensions.height >= preset.dimensions.height) result = preset; } return result; } static List? computeVideoEncodings({ - int? width, - int? height, + VideoDimensions? dimensions, VideoPublishOptions? options, }) { options ??= const VideoPublishOptions(); VideoEncoding? videoEncoding = options.videoEncoding; - if ((videoEncoding == null && !options.simulcast) || - width == null || - height == null) { + if ((videoEncoding == null && !options.simulcast) || dimensions == null) { // don't set encoding when we are not simulcasting and user isn't restricting // encoding parameters return null; } - final presets = _presetsForResolution(width, height); + final presets = _presetsForDimensions(dimensions); if (videoEncoding == null) { // find the right encoding based on width/height - final preset = _findPresetForResolution(width, height, presets: presets); + final preset = _findPresetForDimensions(dimensions, presets: presets); // print('Using preset: ${preset.id}'); videoEncoding = preset.encoding; // log.debug('using video encoding', videoEncoding); @@ -118,7 +115,7 @@ class Utils { ), // if resolution is high enough, we would send both h and q res.. // otherwise only send h - if (width >= 960) ...[ + if (dimensions.max() >= 960) ...[ midPreset.encoding.toRTCRtpEncoding( rid: 'h', // passing decimals to hardware encoder of android devices