Relay Media(PK Host) - Flutter
Overview​
The Relay Media feature enables hosts to relay their media (audio, video, screen share) to one or multiple other live streams. This creates cross-stream interactions similar to "PK battles" or collaborative broadcasts.
This feature allows audiences in one live stream to view and hear hosts from another live stream without changing rooms, creating powerful interactive experiences.
Key Concepts​
- Media Relay: The process of transmitting a host's audio/video from one live stream to another
- Source Meeting: The original live stream where the host is broadcasting
- Destination Meeting: The target live stream(s) where the host's media will be relayed
- Relay Kinds: The types of media that can be relayed (video, audio, screen share, etc.)
Sequence Diagram​
Implementation Guide​
1. Requesting Media Relay​
requestMediaRelay()
​
Parameters:
destinationlivestreamId
(string): ID of the target liveStreamtoken
(string, optional): Authentication token for the destination liveStreamkinds
(array, optional): Array of media types to relay. Options:"video"
: Camera video"audio"
: Microphone audio"share"
: Screen share video"share_audio"
: Screen share audio
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatelessWidget {
final Room livestream;
final String token;
const LiveStreamView({
Key? key,
required this.livestream,
required this.token,
}) : super(key: key);
void _handleRequestMediaRelay(BuildContext context) {
livestream.requestMediaRelay(
"LiveStream-Id", // Replace with your actual livestreamID or target stream ID
token,
["video", "audio"], // Add other media types if needed
);
Navigator.pop(context); // Optional: navigate back after requesting
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: const Text('Request Media Relay'),
onPressed: () => _handleRequestMediaRelay(context),
);
}
}
2. Handling Relay Requests (Destination Meeting)​
In the destination liveStream, hosts (participants with SEND_AND_RECV
mode) will receive relay requests:
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatefulWidget {
final Room livestream;
const LiveStreamView({Key? key, required this.livestream}) : super(key: key);
@override
State<LiveStreamView> createState() => _LiveStreamViewState();
}
class _LiveStreamViewState extends State<LiveStreamView> {
late Room _room;
String? relaylivestreamId;
String? relayPeerId;
@override
void initState() {
super.initState();
_room = widget.livestream;
_room.on(Events.mediaRelayRequestReceived, (livestreamId, peerId, displayname, accept, reject) {
print("Media relay received: $livestreamId");
setState(() {
relaylivestreamId = livestreamId;
relayPeerId = peerId;
});
_showRelayRequestDialog(
context,
displayname,
relaylivestreamId!,
relayPeerId!,
accept,
reject
);
});
}
void _showRelayRequestDialog(
BuildContext context,
String displayName,
String livestreamId,
String peerId,
VoidCallback accept,
VoidCallback reject,
) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Media Relay Request'),
content: Text(
'$displayName wants to relay media in livestream $livestreamId. Allow?'),
actions: [
TextButton(
child: const Text('Allow'),
onPressed: () {
accept(); // Call the provided accept callback
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Deny'),
onPressed: () {
reject(); // Call the provided reject callback
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}
3. Handling Request Responses (Source Meeting)​
In the source liveStream, track the response to your relay requests:
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatefulWidget {
final Room livestream;
const LiveStreamView({Key? key, required this.livestream}) : super(key: key);
@override
State<LiveStreamView> createState() => _LiveStreamViewState();
}
class _LiveStreamViewState extends State<LiveStreamView> {
late Room _room;
String relayStatusMessage = "";
@override
void initState() {
super.initState();
_room = widget.livestream;
_room.on(Events.mediaRelayRequestResponse, (decisionBy, decision, livestreamId) {
final participantId = decisionBy;
final _decision = decision
if (_decision == "accepted") {
print("Relay request accepted by $participantId");
setState(() {
relayStatusMessage = "Relay accepted by $participantId";
});
} else {
print("Relay request rejected by $participantId");
setState(() {
relayStatusMessage = "Relay rejected by $participantId";
});
}
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Text(
relayStatusMessage.isNotEmpty
? relayStatusMessage
: "Waiting for relay response...",
style: const TextStyle(fontSize: 16),
),
);
}
}
4. Tracking Active Relays​
When a relay successfully starts:
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatefulWidget {
final Room livestream;
const LiveStreamView({Key? key, required this.livestream}) : super(key: key);
@override
State<LiveStreamView> createState() => _LiveStreamViewState();
}
class _LiveStreamViewState extends State<LiveStreamView> {
late Room _room;
String? activeRelayId;
@override
void initState() {
super.initState();
_room = widget.livestream;
_room.on(Events.mediaRelayStarted, (livestreamId) {
final String _liveStreamId = livestreamId;
print("Media relay started to $liveStreamId");
setState(() {
activeRelayId = liveStreamId;
});
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Text(
activeRelayId != null
? "Media relay active to: $activeRelayId"
: "No active relay.",
style: const TextStyle(fontSize: 16),
),
);
}
}
5. Stopping Media Relay​
To stop an ongoing relay:
stopMediaRelay()
​
Parameters:
liveStreamId
(string): ID of the liveStream where the relay should stop
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatelessWidget {
final Room livestream;
final String token;
const LiveStreamView({
Key? key,
required this.livestream,
required this.token,
}) : super(key: key);
void _handleStopMediaRelay(BuildContext context) {
meeting.stopMediaRelay();
Navigator.pop(context); // Optional: navigate back after requesting
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: const Text('Stop Relay'),
onPressed: () => _handleStopMediaRelay(context),
);
}
}
6. Handling Relay Stop Events​
When a relay stops for any reason:
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatefulWidget {
final Room livestream;
final String token;
const LiveStreamView({
Key? key,
required this.livestream,
required this.token,
}) : super(key: key);
@override
State<LiveStreamView> createState() => _LiveStreamViewState();
}
class _LiveStreamViewState extends State<LiveStreamView> {
late Room _room;
@override
void initState() {
super.initState();
_room = widget.livestream;
registerEvents();
}
void registerEvents() {
_room.on(Events.mediaRelayStopped, (livestreamId, reason) {
String _reason = reason;
switch (_reason) {
case "user_stopped":
toastMsg("Media relay stopped");
break;
case "connection_lost":
toastMsg("Media relay ended");
break;
default:
toastMsg("Media relay stopped");
}
});
}
void _handleRequestMediaRelay(BuildContext context) {
_room.stopMediaRelay("<Livestream-Id>");
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: const Text('Request Media Relay'),
onPressed: () => _handleRequestMediaRelay(context),
);
}
}
7. Handling Relay Errors​
To handle errors that may occur during relay:
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamView extends StatefulWidget {
final Room livestream;
final String token;
const LiveStreamView({
Key? key,
required this.livestream,
required this.token,
}) : super(key: key);
@override
State<LiveStreamView> createState() => _LiveStreamViewState();
}
class _LiveStreamViewState extends State<LiveStreamView> {
late Room _room;
@override
void initState() {
super.initState();
_room = widget.livestream;
registerEvents();
}
void registerEvents() {
_room.on(Events.mediaRelayError, (livestreamId, reason) {
print("Media relay error $reason");
});
}
void _handleRequestMediaRelay(BuildContext context) {
_room.stopMediaRelay("<LivestreamId>");
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: const Text('Request Media Relay'),
onPressed: () => _handleRequestMediaRelay(context),
);
}
}
Use Cases​
- Guest Appearances: Allow popular hosts to make guest appearances in other streams without leaving their audience
- Cross-Stream Competitions: Create "battles" or competitions between hosts in different streams
- Multi-Location Broadcasting: Connect hosts from different physical locations into a shared experience
- Expert Commentary: Bring in subject matter experts to comment on events in another stream
Troubleshooting​
Common Issues​
-
Relay Request Not Received
- Verify both meetings are active
- Check that destination liveStream ID is correct
- Ensure the token has proper permissions
-
Media Not Visible After Acceptance
- Verify network connectivity
- Check that appropriate media kinds were specified
- Ensure the host has enabled their camera/microphone
-
Unexpected Relay Termination
- Check for network connectivity issues
- Verify that both meetings remain active
- Look for error events with specific reasons
API Reference​
Got a Question? Ask us on discord