publishVideoTrack method

Future<LocalTrackPublication<LocalVideoTrack>> publishVideoTrack(
  1. LocalVideoTrack track,
  2. {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 (videoTracks.any(
      (e) => e.track?.mediaStreamTrack.id == track.mediaStreamTrack.id)) {
    throw TrackPublishException('track already exists');
  }

  // Use defaultPublishOptions if options is null
  publishOptions =
      publishOptions ?? room.roomOptions.defaultVideoPublishOptions;

  if (publishOptions.videoCodec.toLowerCase() != publishOptions.videoCodec) {
    publishOptions = publishOptions.copyWith(
      videoCodec: publishOptions.videoCodec.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.backupCodec == null) {
      publishOptions = publishOptions.copyWith(
        backupCodec: BackupVideoCodec(),
      );
    }
    if (publishOptions.scalabilityMode == null) {
      publishOptions = publishOptions.copyWith(
        scalabilityMode: 'L3T3_KEY',
      );
    }
  }

  // use constraints passed to getUserMedia by default
  VideoDimensions dimensions = track.currentOptions.params.dimensions;

  if (kIsWeb) {
    // getSettings() is only implemented for Web
    try {
      // try to use getSettings for more accurate resolution
      final settings = track.mediaStreamTrack.getSettings();
      if (settings['width'] is int) {
        dimensions = dimensions.copyWith(width: settings['width'] as int);
      }
      if (settings['height'] is 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
  final 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 layers = Utils.computeVideoLayers(
    dimensions,
    encodings,
    isSVC,
  );

  logger.fine('Video layers: ${layers.map((e) => e)}');
  var simulcastCodecs = <lk_rtc.SimulcastCodec>[];

  if (publishOptions.backupCodec != null &&
      publishOptions.backupCodec!.codec != publishOptions.videoCodec) {
    simulcastCodecs = <lk_rtc.SimulcastCodec>[
      lk_rtc.SimulcastCodec(
        codec: publishOptions.videoCodec,
        cid: track.getCid(),
      ),
      lk_rtc.SimulcastCodec(
        codec: publishOptions.backupCodec!.codec.toLowerCase(),
        cid: '',
      ),
    ];
  } else {
    simulcastCodecs = <lk_rtc.SimulcastCodec>[
      lk_rtc.SimulcastCodec(
        codec: publishOptions.videoCodec,
        cid: track.getCid(),
      ),
    ];
  }

  final trackInfo = await room.engine.addTrack(
    cid: track.getCid(),
    name: publishOptions.name ??
        (track.source == TrackSource.screenShareVideo
            ? VideoPublishOptions.defaultScreenShareName
            : VideoPublishOptions.defaultCameraName),
    kind: track.kind,
    source: track.source.toPBType(),
    dimensions: dimensions,
    videoLayers: layers,
    simulcastCodecs: simulcastCodecs,
    videoCodec: publishOptions.videoCodec,
  );

  logger.fine('publishVideoTrack addTrack response: ${trackInfo}');

  await track.start();

  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;
  }

  // prefer to maintainResolution for screen share
  if (track.source == TrackSource.screenShareVideo) {
    var sender = track.transceiver!.sender;
    var parameters = sender.parameters;
    parameters.degradationPreference =
        rtc.RTCDegradationPreference.MAINTAIN_RESOLUTION;
    await sender.setParameters(parameters);
  }

  if (kIsWeb &&
      lkBrowser() == BrowserType.firefox &&
      track.kind == lk_models.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();

  final pub = LocalTrackPublication<LocalVideoTrack>(
    participant: this,
    info: trackInfo,
    track: track,
  );
  addTrackPublication(pub);
  pub.backupVideoCodec = publishOptions.backupCodec;

  // did publish
  await track.onPublish();

  [events, room.events].emit(LocalTrackPublishedEvent(
    participant: this,
    publication: pub,
  ));

  return pub;
}