feat: audio output select for web. (#247)
* feat: audio output select for web. * chore: Add comments for VideoTrackRenderer. * update.
This commit is contained in:
@@ -317,43 +317,45 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
||||
icon: const Icon(EvaIcons.micOff),
|
||||
tooltip: 'un-mute audio',
|
||||
),
|
||||
PopupMenuButton<MediaDevice>(
|
||||
icon: const Icon(Icons.volume_up),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
const PopupMenuItem<MediaDevice>(
|
||||
value: null,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
EvaIcons.speaker,
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text('Select Audio Output'),
|
||||
),
|
||||
),
|
||||
if (_audioOutputs != null)
|
||||
..._audioOutputs!.map((device) {
|
||||
return PopupMenuItem<MediaDevice>(
|
||||
value: device,
|
||||
child: ListTile(
|
||||
leading: (device.deviceId ==
|
||||
Hardware.instance.selectedAudioOutput?.deviceId)
|
||||
? const Icon(
|
||||
EvaIcons.checkmarkSquare,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
EvaIcons.square,
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text(device.label),
|
||||
if (!lkPlatformIs(PlatformType.web))
|
||||
PopupMenuButton<MediaDevice>(
|
||||
icon: const Icon(Icons.volume_up),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
const PopupMenuItem<MediaDevice>(
|
||||
value: null,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
EvaIcons.speaker,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => _selectAudioOutput(device),
|
||||
);
|
||||
}).toList()
|
||||
];
|
||||
},
|
||||
),
|
||||
title: Text('Select Audio Output'),
|
||||
),
|
||||
),
|
||||
if (_audioOutputs != null)
|
||||
..._audioOutputs!.map((device) {
|
||||
return PopupMenuItem<MediaDevice>(
|
||||
value: device,
|
||||
child: ListTile(
|
||||
leading: (device.deviceId ==
|
||||
Hardware
|
||||
.instance.selectedAudioOutput?.deviceId)
|
||||
? const Icon(
|
||||
EvaIcons.checkmarkSquare,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
EvaIcons.square,
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text(device.label),
|
||||
),
|
||||
onTap: () => _selectAudioOutput(device),
|
||||
);
|
||||
}).toList()
|
||||
];
|
||||
},
|
||||
),
|
||||
if (participant.isCameraEnabled())
|
||||
PopupMenuButton<MediaDevice>(
|
||||
icon: const Icon(EvaIcons.video),
|
||||
|
||||
@@ -82,12 +82,20 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget>
|
||||
VideoTrack? get activeVideoTrack;
|
||||
TrackPublication? get videoPublication;
|
||||
TrackPublication? get firstAudioPublication;
|
||||
List<MediaDevice>? _audioOutputs;
|
||||
MediaDevice? _selectedAudioDevice;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.participant.addListener(_onParticipantChanged);
|
||||
_onParticipantChanged();
|
||||
Hardware.instance.audioOutputs().then((value) {
|
||||
setState(() {
|
||||
_audioOutputs = value;
|
||||
_selectedAudioDevice = _audioOutputs?.firstOrNull;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -108,6 +116,14 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget>
|
||||
// since the updated values are computed properties.
|
||||
void _onParticipantChanged() => setState(() {});
|
||||
|
||||
void _onSelectAudioOutput(MediaDevice device) {
|
||||
var audioTrack = firstAudioPublication?.track as RemoteAudioTrack;
|
||||
audioTrack.setAudioOutput(device.deviceId);
|
||||
setState(() {
|
||||
_selectedAudioDevice = device;
|
||||
});
|
||||
}
|
||||
|
||||
// Widgets to show above the info bar
|
||||
List<Widget> extraWidgets(bool isScreenShare) => [];
|
||||
|
||||
@@ -130,10 +146,8 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget>
|
||||
InkWell(
|
||||
onTap: () => setState(() => _visible = !_visible),
|
||||
child: activeVideoTrack != null && !activeVideoTrack!.muted
|
||||
? VideoTrackRenderer(
|
||||
activeVideoTrack!,
|
||||
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
|
||||
)
|
||||
? VideoTrackRenderer(activeVideoTrack!,
|
||||
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain)
|
||||
: const NoVideoWidget(),
|
||||
),
|
||||
|
||||
@@ -144,7 +158,9 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget>
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...extraWidgets(widget.isScreenShare),
|
||||
...extraWidgets(
|
||||
widget.isScreenShare,
|
||||
),
|
||||
ParticipantInfoWidget(
|
||||
title: widget.participant.name.isNotEmpty
|
||||
? '${widget.participant.name} (${widget.participant.identity})'
|
||||
@@ -211,6 +227,13 @@ class _RemoteParticipantWidgetState
|
||||
pub: firstAudioPublication!,
|
||||
icon: EvaIcons.volumeUp,
|
||||
),
|
||||
if (lkPlatformIs(PlatformType.web))
|
||||
RemoteTrackAudioOutputSelectMenuWidget(
|
||||
audioOutputs: _audioOutputs ?? [],
|
||||
selected: _selectedAudioDevice,
|
||||
onSelected: _onSelectAudioOutput,
|
||||
icon: EvaIcons.speaker,
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
@@ -253,3 +276,40 @@ class RemoteTrackPublicationMenuWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class RemoteTrackAudioOutputSelectMenuWidget extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final List<MediaDevice> audioOutputs;
|
||||
final MediaDevice? selected;
|
||||
final Function(MediaDevice) onSelected;
|
||||
const RemoteTrackAudioOutputSelectMenuWidget({
|
||||
required this.audioOutputs,
|
||||
required this.onSelected,
|
||||
required this.selected,
|
||||
required this.icon,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Material(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
child: PopupMenuButton<Function>(
|
||||
tooltip: 'Select AudioOutput',
|
||||
icon: Icon(icon),
|
||||
onSelected: (value) => value(),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<Function>>[
|
||||
...audioOutputs
|
||||
.map((e) => PopupMenuItem(
|
||||
child: ListTile(
|
||||
trailing: (e.deviceId == selected?.deviceId)
|
||||
? const Icon(Icons.check, color: Colors.white)
|
||||
: null,
|
||||
title: Text(e.label),
|
||||
),
|
||||
value: () => onSelected(e),
|
||||
))
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user