Skip to main content
Version: 5.x.x

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.

Key Concepts​

  • Media Relay: The process of transmitting a host's audio/video from one live stream to another
  • Source Room: The original live stream where the host is broadcasting
  • Destination Room: 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:

  • destinationRoomId (String): ID of the target liveStream
  • token (String, optional): Authentication token for the destination liveStream
  • kinds (List<StreamKind>, optional): Media types to relay. If you leave it out, all of them are relayed. Options:
    • StreamKind.VIDEO: Camera video
    • StreamKind.AUDIO: Microphone audio
    • StreamKind.SHARE: Screen share video

Call requestMediaRelay() after you join the liveStream. It returns a Future that throws a VideoSDKError when the SDK refuses the request, so await it inside try/catch.

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

Future<void> _handleRequestMediaRelay(BuildContext context) async {
try {
await livestream.requestMediaRelay(
"LiveStream-Id", // Replace with your actual livestreamID or target stream ID
token: token,
kinds: [StreamKind.VIDEO, StreamKind.AUDIO], // Add other media types if needed
);
} on VideoSDKError catch (e) {
debugPrint("requestMediaRelay failed: ${e.code} ${e.name}");
return;
}
if (context.mounted) {
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 Room)​

In the destination liveStream, hosts (participants with SEND_AND_RECV mode) receive the request on Events.mediaRelayRequestReceived. It delivers the requesting participant's id, the source room id, their display name, and accept and reject functions. Call one of them. The answer reaches the requester in the source liveStream, as the next step shows.

_room.on(
Events.mediaRelayRequestReceived,
(
String participantId,
String roomId,
String displayName,
Function accept,
Function reject,
) {
print("$displayName wants to relay media from $roomId");
accept(); // or reject();
},
);

3. Handling Request Responses (Source Room)​

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,
(String decisionBy, RelayDecision? decision, String? roomId) {
if (!mounted) return;
final participantId = decisionBy;

if (decision == RelayDecision.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, (String roomId) {
if (!mounted) return;
print("Media relay started to $roomId");

setState(() {
activeRelayId = roomId;
});
});
}

@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:

  • destinationRoomId (String): ID of the liveStream where the relay should stop

Like requestMediaRelay(), it returns a Future that throws a VideoSDKError when the SDK refuses the call.

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

Future<void> _handleStopMediaRelay(BuildContext context) async {
try {
await livestream.stopMediaRelay("LiveStream-Id"); // The liveStream you relay to
} on VideoSDKError catch (e) {
debugPrint("stopMediaRelay failed: ${e.code} ${e.name}");
return;
}
if (context.mounted) {
Navigator.pop(context); // Optional: navigate back after stopping
}
}

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

const LiveStreamView({
Key? key,
required this.livestream,
}) : 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, (String roomId, String reason) {
if (!mounted) return;
switch (reason) {
case "user_stopped":
toastMsg("Media relay stopped");
break;
case "connection_lost":
toastMsg("Media relay ended");
break;
default:
toastMsg("Media relay stopped");
}
});
}

void toastMsg(String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}

@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}

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;

const LiveStreamView({
Key? key,
required this.livestream,
}) : 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, (String roomId, String reason) {
print("Media relay error $reason");
});
}

@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}

Use Cases​

  1. Guest Appearances: Allow popular hosts to make guest appearances in other streams without leaving their audience
  2. Cross-Stream Competitions: Create "battles" or competitions between hosts in different streams
  3. Multi-Location Broadcasting: Connect hosts from different physical locations into a shared experience
  4. Expert Commentary: Bring in subject matter experts to comment on events in another stream

Troubleshooting​

Common Issues​

  1. Relay Request Not Received

    • Verify both rooms are active
    • Check that destination liveStream ID is correct
    • Ensure the token has proper permissions
    • Check whether requestMediaRelay() threw a VideoSDKError
  2. Media Not Visible After Acceptance

    • Verify network connectivity
    • Check that appropriate media kinds were specified
    • Ensure the host has enabled their camera/microphone
  3. Unexpected Relay Termination

    • Check for network connectivity issues
    • Verify that both rooms remain active
    • Look for error events with specific reasons

API Reference​

Got a Question? Ask us on discord