Client-Side Session Handling and Layout
With a backend URL ready, this module shifts to the browser side of the same flow.
You will now connect backend and client behavior in one path: fetch credentials from your server, initialize the session, publish local media, and render remote streams.
The point of this module is to give you a clear operational map before the frontend exercise. You should know what each part does, where failures usually show up, and what “working” looks like.
What changes on the client side
- The app requests credentials from your backend URL.
- The client initializes the session and connects participants.
- Local media is published, and remote media is rendered in the layout.
Key concepts to keep in mind
These are the two client behaviors that matter most in the exercise.
Publish flow (local participant)
Once you have a Publisher object and a session, the next step is to pass it to the session object to begin streaming.
To publish a stream, add an OTPublisher component as a child of the OTSession object:
<OTSession
applicationId="your-application-id"
sessionId="the session ID"
token="the token">
<OTPublisher/>
</OTSession>
The publisher starts streaming when the client connects to the session. The OTPublisher object dispatches a streamCreated event when it starts streaming to the session. It dispatches an error event if there is an error publishing. Set an eventHandlers prop of the OTPublisher component, and set the streamCreated and error properties of that object to callback functions:
<OTPublisher
eventHandlers={{
streamCreated: () => {
console.log('The publisher started streaming.');
},
error: event => {
console.log('Publisher error:', event);
},
}}/>
Pass the publisher object into the publish() method of a Session object to publish a stream to the session:
publisher = OT.initPublisher('replacementElementId');
session.publish(publisher, function(error) {
if (error) {
console.log(error);
} else {
console.log('Publishing a stream.');
}
});
The second parameter is a completion handler function that is passed an error object if publishing fails. Otherwise the completion handler function is called with no error passed in.
This code assumes that session is a Session object, and that the client has connected to the session. For more information, see Joining a Session.
The Publish object dispatches a streamCreated event when it starts streaming to the session:
var publisher = OT.initPublisher();
session.publish(publisher, function(error) {
if (error) {
console.log(error);
} else {
console.log('Publishing a stream.');
}
});
publisher.on('streamCreated', function (event) {
console.log('The publisher started streaming.');
});
The Publisher object has an element property, which is set to the HTML DOM element containing it.
You can set the maximum bitrate for a published stream. Setting the maximum bitrate can help to reduce bandwidth consumption when a user is connecting from a metered connection. See this documentation.
Pass the publisher object into the publish(PublisherKit publisher) method of a Session object to publish the stream to the session:
mSession.publish(mPublisher);
This code assumes that mSession is a Session object, and that the client has connected to the session. For more information, see Joining a Session.
The PublisherKit.PublisherListener.onStreamCreated(PublisherKit publisher, Stream stream) method is called when the publisher starts streaming to the session:
@Override
public void onStreamCreated(publisher, stream) {
// The publisher started streaming.
}
You can set the maximum bitrate for a published stream. Setting the maximum bitrate can help to reduce bandwidth consumption when a user is connecting from a metered connection. See this documentation.
Pass the publisher object into the OTSession publish(_:error:) method of an OTSession object to publish the stream to the session:
var error: OTError?
session.publish(publisher, error: &error)
if let error = error {
print("publishing failed with error: \(error)");
}
This code assumes that session is a Session object, and that the client has connected to the session. For more information, see Joining a Session.
The OTPublisherDelegate publisher(_:streamCreated:) message is sent when the publisher starts streaming to the session.
Pass the publisher object into the [OTSession publish:error] method of an OTSession object to publish the stream to the session:
OTError* error = nil;
[session publish:publisher error:&error];
if (error) {
NSLog(@"publishing failed with error: (%@)", error);
}
This code assumes that session is a Session object, and that the client has connected to the session. For more information, see Creating a Session.
The [OTPublisherDelegate publisher:streamCreated:] message is sent when the publisher starts streaming to the session.
You can set the maximum bitrate for a published stream. Setting the maximum bitrate can help to reduce bandwidth consumption when a user is connecting from a metered connection. See this documentation.
Pass the publisher object into the Publish(publisher) method of a Session object to publish the stream to the session:
session.Publish(publisher);
This code assumes that session is a Session object, that publisher is a Publisher object and that the client has connected to the session. For more information, see Joining a Session.
The Publisher sends the StreamCreated event when the publisher starts streaming to the session:
publisher.StreamCreated += Publisher_StreamCreated;
session.Publish(publisher);
private void Publisher_StreamCreated(object sender, Publisher.StreamEventArgs e)
{
Console.WriteLine("The publisher started streaming.");
}
You can set the maximum bitrate for a published stream. Setting the maximum bitrate can help to reduce bandwidth consumption when a user is connecting from a metered connection. See this documentation.
When the application connects to a session, the on_connected() callback function of the otc_session_callbacks struct is called (see Joining a Session). In response to this, you can call the otc_session_publish() function to publish a stream to the OpenTok session:
if (otc_session_publish(session, publisher) != OTC_SUCCESS) {
printf("Could not publish successfully.");
}
The otc_session_publish() function takes two arguments:
- The pointer to the
otc_sessionstructure. - The
otc_publisherstructure.
It returns OTC_SUCCESS when it successfully starts publishing a stream to the session. Or it returns an error, and the otc_error callback is called.
You can set the maximum bitrate for a published stream. Setting the maximum bitrate can help to reduce bandwidth consumption when a user is connecting from a metered connection. See this documentation.
Subscribe flow (remote participant)
Use the session to subscribe to a remote stream and render it in your UI.
To subscribe to all streams in the session, add an OTSubscriber object as a chile of the OTSession object:
<OTSession
applicationId="the Application ID"
sessionId="the session ID"
token="the token">
<OTSubscriber/>
</OTSession>
After the client connects to the session, the OTSubscriber object adds views for subscriber videos when other clients streams become available in the session.
The OTSubscriber object dispatches a connected event when a subscriber successfully starts streaming. It dispatches an error event if there is an error subscribing. Set an eventHandlers prop of the OTSubscriber component, and set the connected and error properties of that object to callback functions:
<OTSubscriber
eventHandlers={{
connected: () => {
console.log('The subscriber started streaming.');
},
error: () => {
console.log('The subscriber failed.');
}
}}/>
To subscribe to a stream, pass the Stream object into the subscribe method of the Session object:
session.subscribe(stream, replacementElementId);
The subscribe() method takes the following parameters:
stream—The Stream object.targetElement— (Optional) Defines the DOM element that the Subscriber video replaces.properties— (Optional) A set of properties that customize the appearance of the Subscriber view in the HTML page and select whether to subscribe to audio and video
see Adjusting audio and video.
completionHandler— (Optional) A function that is called asynchronously when the call to thesubscribe()method completes successfully or fails. If the call to thesubscribe()method fails, the completion handler is passed an error object. This object has acodeandmessageproperty that describe the error.
The following code subscribes to all streams, other than those published by your client:
session.on("streamCreated", function(event) {
session.subscribe(event.stream);
});
// Replace with your token:
session.connect(token, function (error) {
if(error) {
// failed to connect
}
});
The insertMode property of the properties parameter of the Session.subscribe() method specifies how the Publisher object will be inserted in the HTML DOM, in relation to the targetElement parameter. You can set this parameter to one of the following values:
"replace"— The Subscriber object replaces contents of the targetElement. This is the default."after"— The Subscriber object is a new element inserted after the targetElement in the HTML DOM. (Both the Subscriber and targetElement have the same parent element.)"before"— The Subscriber object is a new element inserted before the targetElement in the HTML DOM. (Both the Subscriber and targetElement have the same parent element.)"append"— The Subscriber object is a new element added as a child of the targetElement. If there are other child elements, the Publisher is appended as the last child element of the targetElement.
For example, the following code adds a new Subscriber object as a child of a subscriberContainer DOM element:
session.on('streamCreated', function(event) {
var subscriberProperties = {insertMode: 'append'};
var subscriber = session.subscribe(event.stream,
'subscriberContainer',
subscriberProperties,
function (error) {
if (error) {
console.log(error);
} else {
console.log('Subscriber added.');
}
});
});
The Subscriber object has an element property, which is set to the HTML DOM element containing it.
To subscribe to a stream, first instantiate a Subscriber.Builder object by calling the Subscriber.Builder(Context context, Stream stream) constructor. Pass in the Android application context for the Subscriber and the Stream object. Call the build() method to create the Subscriber object. Then call the subscribe() method of the Session object to start subscribing to the stream:
mSubscriber = new Subscriber.Builder(context, stream)
.build();
mSession.subscribe(mSubscriber);
The SubscriberKit.SubscriberListener.onConnected(SubscriberKit subscriber) method is called when the app starts receiving the subscriber's stream. At this point, you can add the subscriber's view (returned by the getView() method of the Subscriber object) as a subview of an android.view.ViewGroup object to display it in the app:
@Override
public void onConnected(subscriber) {
// mViewContainer is an Android View
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
getResources().getDisplayMetrics().widthPixels, getResources()
.getDisplayMetrics().heightPixels);
mViewContainer.addView(mSubscriber.getView(), layoutParams);
}
Unsubscribing from a stream
To stop playing a stream you are subscribed to, call the Session.unsubscribe(Subscriber subscriber) method:
mSession.unsubscribe(mSubscriber);
The Subscriber is disconnected, and its view is removed from its superview.
To subscribe to a stream, call the OTSubscriber init(stream:delegate:) method, passing in an OTStream object and a delegate object to receive subscriber-related messages. Then call theOTSession subscribe(_:error:) method to start subscribing to the stream:
func session(_ session: OTSession, streamCreated stream: OTStream) {
subscriber = OTSubscriber(stream: stream, delegate: self)
var error: OTError?
session.subscribe(subscriber!, error: &error)
if error {
print("subscribe failed with error: \(error)")
}
}
The OTSubscriberDelegate subscriberDidConnect(toStream:) message is sent when the app starts receiving the subscriber's stream. At this point, you can add the subscriber's view (represented by the OTSubscriber view property) to the app:
func subscriberDidConnect(toStream subscriber: OTSubscriberKit) {
if let subscriberView = self.subscriber?.view {
subscriberView.frame = CGRect(x: 0, y: 300, width: 400, height: 300)
self.view.addSubview(subscriberView)
}
}
Unsubscribing from a stream
To stop playing a stream you are subscribed to, call the OTSession unsubscribe(_:error:) method:
var error: OTError?
session.unsubscribe(subscriber, error: &error)
if (error) {
print("unsubscribe failed with error: \(error)")
}
The Subscriber is disconnected. Next, remove its view from its superview:
subscriber.view?.removeFromSuperview()
To subscribe to a stream, call the [OTSubscriber initWithStream:] method, passing in an OTStream object and a delegate object to receive subscriber-related messages. Then call the[OTSession subscribe:error] method to start subscribing to the stream:
- (void)session:(OTSession*)session streamCreated:(OTStream*)stream
{
subscriber = [[OTSubscriber alloc] initWithStream:stream delegate:self];
OTError* error = nil;
[session subscribe:subscriber error:&error]
if (error) {
NSLog(@"subscribe failed with error: (%@)", error);
}
}
The [OTSubscriberDelegate subscriberDidConnectToStream:] message is sent when the app starts receiving the subscriber's stream. At this point, you can add the subscriber's view (represented by the OTSubscriber.view property) to the app:
- (void)subscriberDidConnectToStream:(OTSubscriber*)subscriber
{
[subscriber.view setFrame:CGRectMake(0, 300, 400, 300)];
[self.view addSubview:subscriber.view];
}
Unsubscribing from a stream
To stop playing a stream you are subscribed to, call the [OTSession unsubscribe:error:] method:
OTError* error = nil;
[session unsubscribe:_subscriber error:&error]
if (error) {
NSLog(@"unsubscribe failed with error: (%@)", error);
}
The Subscriber is disconnected. Next, remove its view from its superview:
[subscriber.view removeFromSuperview:];
To subscribe to a stream, first instantiate a Subscriber object by calling the Subscriber(context, stream, renderer) constructor. Pass in the Windows application context for the Subscriber, the Stream object, and a video renderer.
The OpenTok.IVideoRenderer interface defines the video renderer. The OpenTok.VideoRenderer class, included in Vonage Video Windows SDK, renders video to an Windows Presentation Framework control. The VideoRenderer object is a subclass of System.Windows.Controls.Control.
You can add this element to your view hierarchy. Or you can create your own custom video renderer that implements the OpenTok.IVideoRenderer interface.
Call the subscribe() method of the Session object to start subscribing to the stream:
VideoRenderer videoRenderer = new VideoRenderer();
// Add the video renderer to the application's view hierarchy.
Subscriber subscriber = new Subscriber(Context.Instance, stream, renderer);
subscriber.Error += Subscriber_Error;
session.subscribe(subscriber);
private void Session_Error(object sender, Subscriber.ErrorEventArgs e)
{
Console.WriteLine("Subscriber error:" + e.ErrorCode);
}
The Subscriber object sends an Error event if there is an error in subscribing to the stream. Check the ErrorCode property of the arguments passed into the event to see details on why subscribing failed. (The OpenTok.ErrorCode enum defines ErrorCode values.)
The Subscriber object sends a Connected event when the app starts receiving the subscriber's stream.
Note: The OpenTok.Subscriber class implements the System.IDisposable interface. Be sure to call the Dispose() method of the Subscriber object to release its resources when you no longer need the object (for example, when the Subscriber stops streaming video or when the app or window is closing).
You can create a custom audio driver to be used by all publishers and subscribers.
Unsubscribing from a stream
To stop playing a stream you are subscribed to, call the Session.Unsubscribe(subscriber) method:
session.unsubscribe(subscriber);
The Subscriber is disconnected, and its view is removed from its superview.
The on_stream_received callback function (see the previous section) includes a stream parameter, which is a pointer to an otc_stream struct representing the new stream. To subscribe to the stream, instantiate a otc_subscriber_callbacks instance, set some callback functions for subscriber-related events, and then call the otc_subscriber_new() function passing in the otc_stream and otc_subscriber_callbacks instances
char *user_data = strdup("Session user data");
static void on_subscriber_connected(otc_subscriber *subscriber,
void *user_data,
const otc_stream *stream) {
// Called when the subscriber connects to the stream.
}
static void on_render_frame(otc_subscriber *subscriber,
void *user_data,
const otc_video_frame *frame) {
// Called when the subscriber is ready to render a new video frame
}
static void on_error(otc_subscriber *subscriber,
void *user_data) {
// Called when there is an error.
}
struct otc_subscriber_callbacks subscriber_callbacks = {0};
subscriber_callbacks.user_data = user_data;
subscriber_callbacks.on_connected = on_subscriber_connected;
subscriber_callbacks.on_render_frame = on_subscriber_render_frame;
subscriber_callbacks.on_error = on_subscriber_error;
otc_subscriber *subscriber = otc_subscriber_new(stream,
&subscriber_callbacks);
if (subscriber == NULL) {
printf("Could not create OpenTok subscriber successfully");
return;
}
if (otc_session_subscribe(session, subscriber) != OTC_SUCCESS) {
printf("Could not subscribe successfully.");
}
Use the user_data member of the otc_subscriber_callbacks structure to set data you may want to reference in the callback functions. In this example, we set it to a pointer to a string object. But it could be a pointer to an instance of some other type that contains meaningful information.
The other members of the otc_subscriber_callbacks structure are each callback functions that are invoked when events related to the subscriber occur:
on_connected— Called when the subscriber connects to the audio-video stream.on_render_frame— Called each time the subscriber is ready to render a new video frame.on_error— Called when a subscriber error occurs.
All callbacks will not be made on the application or main thread but on an internal thread. The application should return the callback as quickly as possible to avoid blocking the internal thread.
See otc_subscriber_callbacks in the Vonage Video Linux SDK reference for details on each of the callback functions.
Unsubscribing from a stream
To stop playing a stream you are subscribed to, call the otc_session_unsubscribe() function, passing in the otc_session and otc_subscriber instances:
if (session.unsubscribe(session, subscriber) == OTC_SUCCESS) {
printf("Unsubscribed from the stream successfully.");
otc_subscriber_delete(subscriber);
} else {
printf("Could not unsubscribe successfully.");
};
Call the otc_subscriber_delete() function to release the subscriber instance, including all hardware and UI resources bound to it.
The key idea is simple: local media must publish cleanly, and remote media must subscribe and render predictably.
If either side breaks, the first debug step is to check whether credentials loaded correctly from your backend URL.
If you want deeper implementation details, use: