publishVideoTrack method
- LocalVideoTrack track, {
- VideoPublishOptions? publishOptions,
Publish a LocalVideoTrack to the Room. For most cases, using setCameraEnabled would be simpler and recommended.
Implementation
Future<LocalTrackPublication<LocalVideoTrack>> publishVideoTrack(
LocalVideoTrack track, {
VideoPublishOptions? publishOptions,
}) async {
if (videoTrackPublications.any((e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
throw TrackPublishException('track already exists');
}
// Use defaultPublishOptions if options is null
publishOptions ??= track.lastPublishOptions ?? room.roomOptions.defaultVideoPublishOptions;
if (publishOptions.videoCodec.toLowerCase() != publishOptions.videoCodec) {
publishOptions = publishOptions.copyWith(
videoCodec: publishOptions.videoCodec.toLowerCase(),
);
}
if (room.engine.enabledPublishCodecs?.isNotEmpty ?? false) {
// fallback to a supported codec if it is not supported
if (!room.engine.enabledPublishCodecs!
.where((c) => c.mime.startsWith('video/'))
.where((c) => videoCodecs.any((v) => c.mime.toLowerCase().endsWith(v)))
.any((c) => publishOptions?.videoCodec == mimeTypeToVideoCodecString(c.mime))) {
publishOptions = publishOptions.copyWith(
videoCodec: mimeTypeToVideoCodecString(room.engine.enabledPublishCodecs![0].mime).toLowerCase(),
);
}
}
// handle SVC publishing
final isSVC = isSVCCodec(publishOptions.videoCodec);
if (isSVC) {
if (!room.roomOptions.dynacast) {
room.engine.roomOptions = room.roomOptions.copyWith(dynacast: true);
}
if (publishOptions.scalabilityMode == null) {
publishOptions = publishOptions.copyWith(
scalabilityMode: 'L3T3_KEY',
);
}
// vp9 svc with screenshare has problem to encode, always use L1T3 here
if (track.source == TrackSource.screenShareVideo) {
publishOptions = publishOptions.copyWith(
scalabilityMode: 'L1T3',
);
}
}
// use finalraints passed to getUserMedia by default
VideoDimensions dimensions = track.currentOptions.params.dimensions;
if (kIsWeb || lkPlatformIsMobile()) {
// getSettings() is only implemented for Web & Mobile
try {
// try to use getSettings for more accurate resolution
final settings = track.mediaStreamTrack.getSettings();
if ((settings['width'] is int && settings['width'] as int > 0) &&
(settings['height'] is int && settings['height'] as int > 0)) {
dimensions = dimensions.copyWith(width: settings['width'] as int);
dimensions = dimensions.copyWith(height: settings['height'] as int);
}
} catch (_) {
logger.warning('Failed to call `mediaStreamTrack.getSettings()`');
}
}
logger.fine('Compute encodings with resolution: ${dimensions}, options: ${publishOptions}');
// Video encodings and simulcasts
var encodings = Utils.computeVideoEncodings(
isScreenShare: track.source == TrackSource.screenShareVideo,
dimensions: dimensions,
options: publishOptions,
codec: publishOptions.videoCodec,
);
logger.fine('Using encodings: ${encodings?.map((e) => e.toMap())}');
final simulcastCodecs = <lk_rtc.SimulcastCodec>[
lk_rtc.SimulcastCodec(
codec: publishOptions.videoCodec,
cid: track.getCid(),
),
];
if (publishOptions.backupVideoCodec.enabled && publishOptions.backupVideoCodec.codec != publishOptions.videoCodec) {
simulcastCodecs.add(lk_rtc.SimulcastCodec(
codec: publishOptions.backupVideoCodec.codec.toLowerCase(),
cid: '',
));
}
final layers = Utils.computeVideoLayers(
dimensions,
encodings,
isSVC,
);
if (room.engine.isClosed) {
throw UnexpectedConnectionState('cannot publish track when not connected');
}
logger.fine('Video layers: ${layers.map((e) => e)}');
Future<lk_models.TrackInfo> negotiate() async {
track.transceiver = await room.engine.createTransceiverRTCRtpSender(track, publishOptions!, encodings);
if (lkBrowser() != BrowserType.firefox) {
await room.engine.setPreferredCodec(
track.transceiver!,
'video',
publishOptions.videoCodec,
);
track.codec = publishOptions.videoCodec;
}
if ([TrackSource.camera, TrackSource.screenShareVideo].contains(track.source)) {
final degradationPreference = publishOptions.degradationPreference ??
getDefaultDegradationPreference(
track,
);
await track.setDegradationPreference(degradationPreference);
}
if (kIsWeb && lkBrowser() == BrowserType.firefox && track.kind == TrackType.AUDIO) {
//TOOD:
} else if (isSVCCodec(publishOptions.videoCodec) && encodings?.first.maxBitrate != null) {
room.engine.publisher?.setTrackBitrateInfo(TrackBitrateInfo(
cid: track.getCid(),
transceiver: track.transceiver,
codec: publishOptions.videoCodec,
maxbr: encodings![0].maxBitrate! ~/ 1000));
}
await room.engine.negotiate();
return lk_models.TrackInfo();
}
final req = lk_rtc.AddTrackRequest(
cid: track.getCid(),
name: publishOptions.name ??
(track.source == TrackSource.screenShareVideo
? VideoPublishOptions.defaultScreenShareName
: VideoPublishOptions.defaultCameraName),
type: track.kind.toPBType(),
source: track.source.toPBType(),
encryption: room.roomOptions.lkEncryptionType,
simulcastCodecs: simulcastCodecs,
muted: track.muted,
stream: buildStreamId(publishOptions, track.source),
);
// video specific
if (dimensions.width > 0 && dimensions.height > 0) {
req.width = dimensions.width;
req.height = dimensions.height;
}
if (layers.isNotEmpty) {
req.layers
..clear()
..addAll(layers);
}
late lk_models.TrackInfo trackInfo;
if (room.engine.enabledPublishCodecs?.isNotEmpty ?? false) {
final rets = await Future.wait<lk_models.TrackInfo>([room.engine.addTrack(req), negotiate()]);
trackInfo = rets[0];
} else {
trackInfo = await room.engine.addTrack(req);
String? primaryCodecMime;
for (var codec in trackInfo.codecs) {
primaryCodecMime ??= codec.mimeType;
}
if (primaryCodecMime != null) {
final updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
if (updatedCodec != publishOptions.videoCodec) {
logger.fine(
'requested a different codec than specified by serverRequested: ${publishOptions.videoCodec}, server: ${updatedCodec}',
);
publishOptions = publishOptions.copyWith(
videoCodec: updatedCodec,
);
// recompute encodings since bitrates/etc could have changed
encodings = Utils.computeVideoEncodings(
isScreenShare: track.source == TrackSource.screenShareVideo,
dimensions: dimensions,
options: publishOptions,
codec: publishOptions.videoCodec,
);
}
}
final transceiverInit = rtc.RTCRtpTransceiverInit(
direction: rtc.TransceiverDirection.SendOnly,
sendEncodings: encodings,
);
logger.fine('publishVideoTrack publisher: ${room.engine.publisher}');
track.transceiver = await room.engine.publisher?.pc.addTransceiver(
track: track.mediaStreamTrack,
kind: rtc.RTCRtpMediaType.RTCRtpMediaTypeVideo,
init: transceiverInit,
);
if (lkBrowser() != BrowserType.firefox) {
await room.engine.setPreferredCodec(
track.transceiver!,
'video',
publishOptions.videoCodec,
);
track.codec = publishOptions.videoCodec;
}
if ([TrackSource.camera, TrackSource.screenShareVideo].contains(track.source)) {
final degradationPreference = publishOptions.degradationPreference ??
getDefaultDegradationPreference(
track,
);
await track.setDegradationPreference(degradationPreference);
}
if (kIsWeb && lkBrowser() == BrowserType.firefox && track.kind == TrackType.AUDIO) {
//TOOD:
} else if (isSVCCodec(publishOptions.videoCodec) && encodings?.first.maxBitrate != null) {
room.engine.publisher?.setTrackBitrateInfo(TrackBitrateInfo(
cid: track.getCid(),
transceiver: track.transceiver,
codec: publishOptions.videoCodec,
maxbr: encodings![0].maxBitrate! ~/ 1000));
}
await room.engine.negotiate();
}
logger.fine('publishVideoTrack engine.addTrack response: ${trackInfo}');
track.lastPublishOptions = publishOptions;
await track.start();
final pub = LocalTrackPublication<LocalVideoTrack>(
participant: this,
info: trackInfo,
track: track,
);
addTrackPublication(pub);
pub.backupVideoCodec = publishOptions.backupVideoCodec;
// did publish
await track.onPublish();
await track.processor?.onPublish(room);
final listener = track.createListener();
listener.on((TrackEndedEvent event) async {
logger.fine('TrackEndedEvent: ${event.track}');
await removePublishedTrack(pub.sid);
});
[events, room.events].emit(LocalTrackPublishedEvent(
participant: this,
publication: pub,
));
return pub;
}