Skip to content

Commit f389e26

Browse files
authored
feat(LLC): Support for transcription added to SDK (#701)
* Support for transcription added to SDK * transcription docs
1 parent e78bf8c commit f389e26

25 files changed

+427
-37
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Transcriptions
2+
3+
Enabling your application to provide a transcript for a call can be very useful for your users. We understand though, that this can be a difficult feature to implement/support.
4+
5+
This is why, the Flutter Video SDK comes with out of the box Transcription support that you can easily manage.
6+
7+
First thing you can do is access the transcriptions settings as they have been configured from the dashboard. The `mode` property defines the feature's availability with:
8+
9+
- `available`: the feature is available for your call and can be enabled.
10+
11+
- `disabled`: the feature is not available for your call. In this case, it's a good idea to "hide" any UI element you have related to transcription.
12+
13+
- `autoOn`: the feature is available and it will be enabled automatically, once the user is connected on the call.
14+
15+
Second thing you can do is to start/stop the transcription itself. This can be done by calling the `call.startTranscription()` and `call.stopTranscription()` methods.
16+
17+
Transcription file is saved whenever you stop the transcription or end the call. You can then access the transcription files by calling the `call.listTranscriptions()` method.

dogfooding/lib/core/repos/token_service.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ enum Environment {
1919
demo(
2020
'Demo',
2121
'demo',
22-
'demo.getstream.io',
22+
'pronto.getstream.io',
2323
aliases: [''],
2424
),
2525
staging(

packages/stream_video/lib/src/call/call.dart

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,10 @@ class Call {
389389
return _stateManager.coordinatorCallRecordingStarted(event);
390390
} else if (event is CoordinatorCallRecordingStoppedEvent) {
391391
return _stateManager.coordinatorCallRecordingStopped(event);
392+
} else if (event is CoordinatorCallTranscriptionStartedEvent) {
393+
return _stateManager.coordinatorCallTranscriptionStarted(event);
394+
} else if (event is CoordinatorCallTranscriptionStoppedEvent) {
395+
return _stateManager.coordinatorCallTranscriptionStopped(event);
392396
} else if (event is CoordinatorCallBroadcastingStartedEvent) {
393397
return _stateManager.coordinatorCallBroadcastingStarted(event);
394398
} else if (event is CoordinatorCallBroadcastingStoppedEvent) {
@@ -1482,6 +1486,30 @@ class Call {
14821486
return result;
14831487
}
14841488

1489+
Future<Result<None>> startTranscription() async {
1490+
final result = await _permissionsManager.startTranscription();
1491+
1492+
if (result.isSuccess) {
1493+
_stateManager.setCallTranscribing(isTranscribing: true);
1494+
}
1495+
1496+
return result;
1497+
}
1498+
1499+
Future<Result<List<CallTranscription>>> listTranscriptions() async {
1500+
return _permissionsManager.listTranscriptions();
1501+
}
1502+
1503+
Future<Result<None>> stopTranscription() async {
1504+
final result = await _permissionsManager.stopTranscription();
1505+
1506+
if (result.isSuccess) {
1507+
_stateManager.setCallTranscribing(isTranscribing: false);
1508+
}
1509+
1510+
return result;
1511+
}
1512+
14851513
/// Starts the broadcasting of the call.
14861514
Future<Result<String?>> startHLS() async {
14871515
final result = await _permissionsManager.startBroadcasting();
@@ -1776,7 +1804,8 @@ class Call {
17761804
}
17771805

17781806
Future<Result<None>> setSubscriptions(
1779-
List<SubscriptionChange> changes) async {
1807+
List<SubscriptionChange> changes,
1808+
) async {
17801809
final result = await _session?.setSubscriptions(changes) ??
17811810
Result.error('Session is null');
17821811

packages/stream_video/lib/src/call/call_events.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,38 @@ class StreamCallRecordingStoppedEvent extends StreamCallEvent {
449449
];
450450
}
451451

452+
/// Event that is triggered when the transcription is started for a call.
453+
class StreamCallTranscriptionStartedEvent extends StreamCallEvent {
454+
const StreamCallTranscriptionStartedEvent(
455+
super.callCid, {
456+
required this.createdAt,
457+
});
458+
459+
final DateTime createdAt;
460+
461+
@override
462+
List<Object?> get props => [
463+
...super.props,
464+
createdAt,
465+
];
466+
}
467+
468+
/// Event that is triggered when the transcription is stopped for a call.
469+
class StreamCallTranscriptionStoppedEvent extends StreamCallEvent {
470+
const StreamCallTranscriptionStoppedEvent(
471+
super.callCid, {
472+
required this.createdAt,
473+
});
474+
475+
final DateTime createdAt;
476+
477+
@override
478+
List<Object?> get props => [
479+
...super.props,
480+
createdAt,
481+
];
482+
}
483+
452484
/// Event that is triggered when the broadcasting is started for a call.
453485
class StreamCallBroadcastingStartedEvent extends StreamCallEvent {
454486
const StreamCallBroadcastingStartedEvent(
@@ -811,6 +843,16 @@ extension CoordinatorCallEventX on CoordinatorCallEvent {
811843
event.callCid,
812844
createdAt: event.createdAt,
813845
),
846+
final CoordinatorCallTranscriptionStartedEvent event =>
847+
StreamCallTranscriptionStartedEvent(
848+
event.callCid,
849+
createdAt: event.createdAt,
850+
),
851+
final CoordinatorCallTranscriptionStoppedEvent event =>
852+
StreamCallTranscriptionStoppedEvent(
853+
event.callCid,
854+
createdAt: event.createdAt,
855+
),
814856
final CoordinatorCallBroadcastingStartedEvent event =>
815857
StreamCallBroadcastingStartedEvent(
816858
event.callCid,

packages/stream_video/lib/src/call/permissions/permissions_manager.dart

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ class PermissionsManager {
134134
}
135135

136136
Future<Result<List<CallRecording>>> listRecordings() async {
137-
_logger.d(() => '[queryRecordings] Call $callCid');
137+
_logger.d(() => '[listRecordings] Call $callCid');
138138
final result = await coordinatorClient.listRecordings(callCid);
139-
_logger.v(() => '[queryRecordings] result: $result');
139+
_logger.v(() => '[listRecordings] result: $result');
140140
return result;
141141
}
142142

@@ -151,6 +151,35 @@ class PermissionsManager {
151151
return result;
152152
}
153153

154+
Future<Result<None>> startTranscription() async {
155+
if (!hasPermission(CallPermission.startTranscriptionCall)) {
156+
_logger.w(() => '[startTranscription] rejected (no permission)');
157+
return Result.error('Cannot start transcription (no permission)');
158+
}
159+
_logger.d(() => '[startTranscription] no args');
160+
final result = await coordinatorClient.startTranscription(callCid);
161+
_logger.v(() => '[startTranscription] result: $result');
162+
return result;
163+
}
164+
165+
Future<Result<List<CallTranscription>>> listTranscriptions() async {
166+
_logger.d(() => '[listTranscriptions] Call $callCid');
167+
final result = await coordinatorClient.listTranscriptions(callCid);
168+
_logger.v(() => '[listTranscriptions] result: $result');
169+
return result;
170+
}
171+
172+
Future<Result<None>> stopTranscription() async {
173+
if (!hasPermission(CallPermission.startTranscriptionCall)) {
174+
_logger.w(() => '[stopTranscription] rejected (no permission)');
175+
return Result.error('Cannot stop transcription (no permission)');
176+
}
177+
_logger.d(() => '[stopTranscription] no args');
178+
final result = await coordinatorClient.stopTranscription(callCid);
179+
_logger.v(() => '[stopTranscription] result: $result');
180+
return result;
181+
}
182+
154183
Future<Result<String?>> startBroadcasting() async {
155184
if (!hasPermission(CallPermission.startBroadcastCall)) {
156185
_logger.w(() => '[startBroadcasting] rejected (no permission)');

packages/stream_video/lib/src/call/session/call_session.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,14 +751,17 @@ class CallSession extends Disposable {
751751
VisibilityChange visibilityChange,
752752
) async {
753753
_logger.d(
754-
() => '[updateViewportVisibility] visibilityChange: $visibilityChange');
754+
() => '[updateViewportVisibility] visibilityChange: $visibilityChange',
755+
);
755756
return _vvBuffer.post(visibilityChange);
756757
}
757758

758759
Future<Result<None>> updateViewportVisibilities(
759760
List<VisibilityChange> visibilityChanges,
760761
) async {
761-
_logger.d(() => '[updateViewportVisibilities] changes: $visibilityChanges');
762+
_logger.d(
763+
() => '[updateViewportVisibilities] changes: $visibilityChanges',
764+
);
762765
// Nothing to do here, this is handled by the UI
763766
return const Result.success(none);
764767
}
@@ -767,7 +770,8 @@ class CallSession extends Disposable {
767770
List<SubscriptionChange> subscriptionChanges,
768771
) async {
769772
_logger.d(
770-
() => '[setSubscriptions] subscriptionChanges: $subscriptionChanges');
773+
() => '[setSubscriptions] subscriptionChanges: $subscriptionChanges',
774+
);
771775

772776
final participants = stateManager.callState.callParticipants;
773777
final exclude = {SfuTrackType.video, SfuTrackType.screenShare};
@@ -794,7 +798,8 @@ class CallSession extends Disposable {
794798
SubscriptionChange subscriptionChange,
795799
) async {
796800
_logger.d(
797-
() => '[updateSubscription] subscriptionChange: $subscriptionChange');
801+
() => '[updateSubscription] subscriptionChange: $subscriptionChange',
802+
);
798803
return _saBuffer.post(subscriptionChange);
799804
}
800805

packages/stream_video/lib/src/call/state/mixins/state_call_actions_mixin.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ mixin StateCallActionsMixin on StateNotifier<CallState> {
1919
);
2020
}
2121

22+
void setCallTranscribing({required bool isTranscribing}) {
23+
_logger.e(() => '[setCallTranscribing] isTranscribing:$isTranscribing');
24+
state = state.copyWith(
25+
isTranscribing: isTranscribing,
26+
);
27+
}
28+
2229
void setCallBroadcasting({
2330
required bool isBroadcasting,
2431
String? hlsPlaylistUrl,

packages/stream_video/lib/src/call/state/mixins/state_coordinator_mixin.dart

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import '../../../models/disconnect_reason.dart';
1212
final _logger = taggedLogger(tag: 'SV:CoordNotifier');
1313

1414
mixin StateCoordinatorMixin on StateNotifier<CallState> {
15-
1615
void coordinatorCallAccepted(
1716
CoordinatorCallAcceptedEvent event,
1817
) {
@@ -181,6 +180,40 @@ mixin StateCoordinatorMixin on StateNotifier<CallState> {
181180
);
182181
}
183182

183+
void coordinatorCallTranscriptionStarted(
184+
CoordinatorCallTranscriptionStartedEvent event,
185+
) {
186+
final status = state.status;
187+
if (status is! CallStatusActive) {
188+
_logger.w(
189+
() =>
190+
'[coordinatorCallTranscriptionStarted] rejected (status is not Active)',
191+
);
192+
return;
193+
}
194+
195+
state = state.copyWith(
196+
isTranscribing: true,
197+
);
198+
}
199+
200+
void coordinatorCallTranscriptionStopped(
201+
CoordinatorCallTranscriptionStoppedEvent event,
202+
) {
203+
final status = state.status;
204+
if (status is! CallStatusActive) {
205+
_logger.w(
206+
() =>
207+
'[coordinatorCallTranscriptionStopped] rejected (status is not Active)',
208+
);
209+
return;
210+
}
211+
212+
state = state.copyWith(
213+
isTranscribing: false,
214+
);
215+
}
216+
184217
void coordinatorCallBroadcastingStarted(
185218
CoordinatorCallBroadcastingStartedEvent event,
186219
) {

packages/stream_video/lib/src/call/state/mixins/state_lifecycle_mixin.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
9494
isBackstage: data.metadata.details.backstage,
9595
isBroadcasting: data.metadata.details.broadcasting,
9696
isRecording: data.metadata.details.recording,
97+
isTranscribing: data.metadata.details.transcribing,
9798
);
9899
}
99100

@@ -121,6 +122,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
121122
isBackstage: data.metadata.details.backstage,
122123
isBroadcasting: data.metadata.details.broadcasting,
123124
isRecording: data.metadata.details.recording,
125+
isTranscribing: data.metadata.details.transcribing,
124126
audioOutputDevice: callConnectOptions.audioOutputDevice,
125127
audioInputDevice: callConnectOptions.audioInputDevice,
126128
);
@@ -149,6 +151,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
149151
isBackstage: data.metadata.details.backstage,
150152
isBroadcasting: data.metadata.details.broadcasting,
151153
isRecording: data.metadata.details.recording,
154+
isTranscribing: data.metadata.details.transcribing,
152155
);
153156
}
154157

@@ -182,6 +185,7 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
182185
isBackstage: data.metadata.details.backstage,
183186
isBroadcasting: data.metadata.details.broadcasting,
184187
isRecording: data.metadata.details.recording,
188+
isTranscribing: data.metadata.details.transcribing,
185189
);
186190
}
187191

packages/stream_video/lib/src/coordinator/coordinator_client.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore_for_file: comment_references
2+
13
import '../../open_api/video/coordinator/api.dart' as open;
24
import '../models/call_cid.dart';
35
import '../models/call_metadata.dart';
@@ -143,6 +145,19 @@ abstract class CoordinatorClient {
143145
/// Stops recording for the call described by the given [callCid].
144146
Future<Result<None>> stopRecording(StreamCallCid callCid);
145147

148+
/// Starts transcription for the call described by the given [callCid].
149+
Future<Result<None>> startTranscription(
150+
StreamCallCid callCid, {
151+
String? transcriptionExternalStorage,
152+
});
153+
154+
Future<Result<List<open.CallTranscription>>> listTranscriptions(
155+
StreamCallCid callCid,
156+
);
157+
158+
/// Stops transcription for the call described by the given [callCid].
159+
Future<Result<None>> stopTranscription(StreamCallCid callCid);
160+
146161
/// Starts broadcasting for the call described by the given [callCid].
147162
Future<Result<String?>> startBroadcasting(StreamCallCid callCid);
148163

0 commit comments

Comments
 (0)