Skip to content

Commit

Permalink
Expose egress, rename broadcasting to HLS (#462)
Browse files Browse the repository at this point in the history
* Expose egress, rename broadcasting to HLS

* fix tutorial

* test + model fixes

---------

Co-authored-by: kanat <>
Co-authored-by: Deven Joshi <[email protected]>
  • Loading branch information
kanat and deven98 authored Aug 14, 2023
1 parent 8f516d2 commit 620d281
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 22 deletions.
8 changes: 4 additions & 4 deletions docusaurus/docs/Flutter/02-tutorials/03-livestreaming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -350,15 +350,15 @@ With these two pieces of information, we can update the settings in OBS then sel
### Viewing a livestream (HLS)
The final piece of livestreaming using Stream is support for HLS or HTTP Live Streaming. HLS unlike WebRTC based streaming tends to have a 10 to 20 second delay but offers video buffering under poor network condition.

To enable HLS support, your call must first be placed into “broadcasting” mode using the `call.startBroadcasting()` method.
To enable HLS support, your `call` must first be placed into “broadcasting” mode using the `call.startHLS()` method.

Next, we can obtain the HLS URL by querying the `hlsPlaylistURL` from the result of our create call function:
Next, we can obtain the HLS URL by querying the `hlsPlaylistURL` from `call.state`:

```dart
final result = await call.getOrCreateCall();
final result = await call.startHLS();
if (result.isSuccess) {
final url = result.getDataOrNull()!.data.metadata.details.hlsPlaylistUrl;
final url = call.state.value.egress.hlsPlaylistUrl;
...
}
Expand Down
14 changes: 10 additions & 4 deletions packages/stream_video/lib/src/call/call.dart
Original file line number Diff line number Diff line change
Expand Up @@ -933,17 +933,21 @@ class Call {
return result;
}

Future<Result<None>> startBroadcasting() async {
/// Starts the broadcasting of the call.
Future<Result<String?>> startHLS() async {
final result = await _permissionsManager.startBroadcasting();

if (result.isSuccess) {
_stateManager.setCallBroadcasting(isBroadcasting: true);
_stateManager.setCallBroadcasting(
isBroadcasting: true,
hlsPlaylistUrl: result.getDataOrNull()
);
}

return result;
}

Future<Result<None>> stopBroadcasting() async {
/// Stops the broadcasting of the call.
Future<Result<None>> stopHLS() async {
final result = await _permissionsManager.stopBroadcasting();

if (result.isSuccess) {
Expand Down Expand Up @@ -1106,6 +1110,7 @@ class Call {
return result;
}

/// Starts the livestreaming of the call.
Future<Result<CallMetadata>> goLive({
bool? startHls,
bool? startRecording,
Expand All @@ -1125,6 +1130,7 @@ class Call {
return result;
}

/// Stops the livestreaming of the call.
Future<Result<CallMetadata>> stopLive() async {
final result = await _coordinatorClient.stopLive(callCid);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class PermissionsManager {
return result;
}

Future<Result<None>> startBroadcasting() async {
Future<Result<String?>> startBroadcasting() async {
if (!hasPermission(CallPermission.startBroadcastCall)) {
_logger.w(() => '[startBroadcasting] rejected (no permission)');
return Result.error('Cannot start broadcasting (no permission)');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ mixin StateCallActionsMixin on StateNotifier<CallState> {
);
}

void setCallBroadcasting({required bool isBroadcasting}) {
_logger.e(() => '[setCallBroadcasting] isBroadcasting:$isBroadcasting');
void setCallBroadcasting({
required bool isBroadcasting,
String? hlsPlaylistUrl,
}) {
_logger.e(
() => '[setCallBroadcasting] isBroadcasting:$isBroadcasting'
', hlsPlaylistUrl: $hlsPlaylistUrl',
);
final curEgress = state.egress;
final newEgress = curEgress.copyWith(
hlsPlaylistUrl: hlsPlaylistUrl,
);

state = state.copyWith(
isBroadcasting: isBroadcasting,
egress: newEgress,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
status: stage.data.toCallStatus(state: state, ringing: ringing),
createdByUserId: stage.data.metadata.details.createdBy.id,
settings: stage.data.metadata.settings,
egress: stage.data.metadata.details.egress,
ownCapabilities: stage.data.metadata.details.ownCapabilities.toList(),
callParticipants: stage.data.metadata.toCallParticipants(state),
);
Expand All @@ -95,6 +96,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
createdByUserId: stage.data.metadata.details.createdBy.id,
isRingingFlow: stage.data.ringing,
settings: stage.data.metadata.settings,
egress: stage.data.metadata.details.egress,
ownCapabilities: stage.data.metadata.details.ownCapabilities.toList(),
callParticipants: stage.data.metadata.toCallParticipants(state),
);
Expand All @@ -118,6 +120,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
status: status,
createdByUserId: stage.data.metadata.details.createdBy.id,
settings: stage.data.metadata.settings,
egress: stage.data.metadata.details.egress,
ownCapabilities: stage.data.metadata.details.ownCapabilities.toList(),
callParticipants: stage.data.metadata.toCallParticipants(state),
);
Expand Down
11 changes: 10 additions & 1 deletion packages/stream_video/lib/src/call_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

import 'models/call_cid.dart';
import 'models/call_egress.dart';
import 'models/call_metadata.dart';
import 'models/call_participant_state.dart';
import 'models/call_permission.dart';
Expand All @@ -29,6 +30,7 @@ class CallState extends Equatable {
isTranscribing: false,
isBackstage: false,
settings: const CallSettings(),
egress: const CallEgress(),
videoInputDevice: null,
audioInputDevice: null,
audioOutputDevice: null,
Expand All @@ -55,6 +57,7 @@ class CallState extends Equatable {
isTranscribing: metadata.details.transcribing,
isBackstage: metadata.details.backstage,
settings: metadata.settings,
egress: metadata.details.egress,
videoInputDevice: null,
audioInputDevice: null,
audioOutputDevice: null,
Expand All @@ -80,6 +83,7 @@ class CallState extends Equatable {
required this.isTranscribing,
required this.isBackstage,
required this.settings,
required this.egress,
required this.ownCapabilities,
required this.callParticipants,
required this.videoInputDevice,
Expand All @@ -94,6 +98,7 @@ class CallState extends Equatable {
final String sessionId;
final CallStatus status;
final CallSettings settings;
final CallEgress egress;
final bool isRecording;
final bool isBroadcasting;
final bool isTranscribing;
Expand Down Expand Up @@ -130,6 +135,7 @@ class CallState extends Equatable {
bool? isTranscribing,
bool? isBackstage,
CallSettings? settings,
CallEgress? egress,
RtcMediaDevice? videoInputDevice,
RtcMediaDevice? audioInputDevice,
RtcMediaDevice? audioOutputDevice,
Expand All @@ -148,6 +154,7 @@ class CallState extends Equatable {
isTranscribing: isTranscribing ?? this.isTranscribing,
isBackstage: isBackstage ?? this.isBackstage,
settings: settings ?? this.settings,
egress: egress ?? this.egress,
videoInputDevice: videoInputDevice ?? this.videoInputDevice,
audioInputDevice: audioInputDevice ?? this.audioInputDevice,
audioOutputDevice: audioOutputDevice ?? this.audioOutputDevice,
Expand All @@ -168,6 +175,7 @@ class CallState extends Equatable {
isBroadcasting,
isBackstage,
settings,
egress,
videoInputDevice,
audioInputDevice,
audioOutputDevice,
Expand All @@ -180,7 +188,8 @@ class CallState extends Equatable {
return 'CallState(status: $status, currentUserId: $currentUserId,'
' callCid: $callCid, createdByUserId: $createdByUserId,'
' sessionId: $sessionId, isRecording: $isRecording,'
' settings: $settings, videoInputDevice: $videoInputDevice,'
' settings: $settings, egress: $egress, '
' videoInputDevice: $videoInputDevice,'
' audioInputDevice: $audioInputDevice,'
' audioOutputDevice: $audioOutputDevice,'
' ownCapabilities: $ownCapabilities,'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ abstract class CoordinatorClient {
Future<Result<None>> stopRecording(StreamCallCid callCid);

/// Starts broadcasting for the call described by the given [callCid].
Future<Result<None>> startBroadcasting(StreamCallCid callCid);
Future<Result<String?>> startBroadcasting(StreamCallCid callCid);

/// Stops broadcasting for the call described by the given [callCid].
Future<Result<None>> stopBroadcasting(StreamCallCid callCid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,12 @@ class CoordinatorClientOpenApi extends CoordinatorClient {
}

@override
Future<Result<None>> startBroadcasting(StreamCallCid callCid) async {
Future<Result<String?>> startBroadcasting(StreamCallCid callCid) async {
try {
await defaultApi.startBroadcasting(callCid.type, callCid.id);
return const Result.success(none);
final result = await defaultApi
.startBroadcasting(callCid.type, callCid.id)
.then((it) => it?.playlistUrl);
return Result.success(result);
} catch (e, stk) {
return Result.failure(VideoErrors.compose(e, stk));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '../../../../open_api/video/coordinator/api.dart' as open;
import '../../logger/stream_log.dart';
import '../../models/call_cid.dart';
import '../../models/call_credentials.dart';
import '../../models/call_egress.dart';
import '../../models/call_metadata.dart';
import '../../models/call_permission.dart';
import '../../models/call_reaction.dart';
Expand Down Expand Up @@ -94,7 +95,7 @@ extension EnvelopeExt on open.CallResponse {
return CallMetadata(
cid: StreamCallCid(cid: cid),
details: CallDetails(
hlsPlaylistUrl: egress.hls?.playlistUrl ?? '',
egress: egress.toCallEgress(),
createdBy: createdBy.toCallUser(),
team: team ?? '',
ownCapabilities:
Expand Down Expand Up @@ -125,6 +126,25 @@ extension EnvelopeExt on open.CallResponse {
}
}

extension EgressExt on open.EgressResponse {
CallEgress toCallEgress() {
return CallEgress(
hlsPlaylistUrl: hls?.playlistUrl,
rtmps: rtmps.map((it) => it.toCallRtmp()).toList(),
);
}
}

extension EgressRtmpExt on open.EgressRTMPResponse {
CallEgressRtmp toCallRtmp() {
return CallEgressRtmp(
name: name,
streamKey: streamKey,
url: url,
);
}
}

extension CallSettingsExt on open.CallSettingsResponse {
// TODO open api provides wider settings options
CallSettings toCallSettings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ class CoordinatorClientRetry extends CoordinatorClient {
}

@override
Future<Result<None>> startBroadcasting(StreamCallCid callCid) {
Future<Result<String?>> startBroadcasting(StreamCallCid callCid) {
return _retryManager.execute(
() => _delegate.startBroadcasting(callCid),
(error, nextAttemptDelay) async {
Expand Down
57 changes: 57 additions & 0 deletions packages/stream_video/lib/src/models/call_egress.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:equatable/equatable.dart';

class CallEgress with EquatableMixin {
const CallEgress({
this.hlsPlaylistUrl,
this.rtmps = const [],
});

final String? hlsPlaylistUrl;
final List<CallEgressRtmp> rtmps;

@override
bool? get stringify => true;

@override
List<Object?> get props => [hlsPlaylistUrl];

/// Returns a copy of this [CallEgress] with the given fields
/// replaced with the new values.
CallEgress copyWith({
String? hlsPlaylistUrl,
List<CallEgressRtmp>? rtmps,
}) {
return CallEgress(
hlsPlaylistUrl: hlsPlaylistUrl ?? this.hlsPlaylistUrl,
rtmps: rtmps ?? this.rtmps,
);
}
}

class CallEgressRtmp {
const CallEgressRtmp({
required this.name,
required this.streamKey,
required this.url,
});

final String name;

final String streamKey;

final String url;

/// Returns a copy of this [CallEgressRtmp] with the given fields
/// replaced with the new values.
CallEgressRtmp copyWith({
String? name,
String? streamKey,
String? url,
}) {
return CallEgressRtmp(
name: name ?? this.name,
streamKey: streamKey ?? this.streamKey,
url: url ?? this.url,
);
}
}
7 changes: 4 additions & 3 deletions packages/stream_video/lib/src/models/call_metadata.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

import 'call_cid.dart';
import 'call_egress.dart';
import 'call_permission.dart';
import 'call_settings.dart';

Expand Down Expand Up @@ -34,7 +35,6 @@ class CallMetadata with EquatableMixin {
@immutable
class CallDetails with EquatableMixin {
const CallDetails({
required this.hlsPlaylistUrl,
required this.createdBy,
required this.team,
required this.ownCapabilities,
Expand All @@ -43,6 +43,7 @@ class CallDetails with EquatableMixin {
required this.recording,
required this.backstage,
required this.transcribing,
required this.egress,
required this.custom,
required this.rtmpIngress,
this.startsAt,
Expand All @@ -51,7 +52,6 @@ class CallDetails with EquatableMixin {
this.updatedAt,
});

final String hlsPlaylistUrl;
final CallUser createdBy;
final String team;
final Iterable<CallPermission> ownCapabilities;
Expand All @@ -60,6 +60,7 @@ class CallDetails with EquatableMixin {
final bool recording;
final bool backstage;
final bool transcribing;
final CallEgress egress;
final Map<String, Object> custom;
final String rtmpIngress;
final DateTime? startsAt;
Expand All @@ -69,14 +70,14 @@ class CallDetails with EquatableMixin {

@override
List<Object?> get props => [
hlsPlaylistUrl,
createdBy,
ownCapabilities,
blockedUserIds,
broadcasting,
recording,
backstage,
transcribing,
egress,
custom,
rtmpIngress,
startsAt,
Expand Down
1 change: 1 addition & 0 deletions packages/stream_video/lib/src/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export 'call_cid.dart';
export 'call_created_data.dart';
export 'call_credentials.dart';
export 'call_device.dart';
export 'call_egress.dart';
export 'call_joined_data.dart';
export 'call_metadata.dart';
export 'call_participant_state.dart';
Expand Down
Loading

0 comments on commit 620d281

Please sign in to comment.