Overview
Note: This tutorial comes after Create a basic audio conference application. It is highly recommended to follow this previous tutorial before going through this one.
This little tutorial guides you through adding video features to a conference.
Start the video
Layout editing
Open the index.html
file and create a video-container
div in which the video stream will be dynamically added. Also add a new element named actions
that will contains a start-video-btn
button element :
<div id="app">
<div id="form">...</div>
<div id="video-container"></div>
<div id="actions">
<button id="start-video-btn" disabled="true">Start video</button>
</div>
</div>
Interface linking
Open the ui.js
file and declare the startVideoBtn
in the initUI
function :
const initUI = () => {
...
const startVideoBtn = document.getElementById('start-video-btn');
...
};
Edit the joinButton
onclick function and enable the startVideoBtn
when the user has joined the conference in the onclick
function :
joinButton.onclick = () => {
...
voxeet.createConference({ alias: conferenceAliasValue })
.then((res) => voxeet.joinConference(res.conferenceId))
.then(() => {
// -----> Enable the button here ------>
startVideoBtn.disabled = false;
});
...
};
Then write the onclick handler of the startVideoBtn
:
startVideoBtn.onclick = () => {
voxeet.startVideoForUser(voxeet.userId)
.then(() => {
startVideoBtn.disabled = true;
});
};
Logic implementation
Open the client.js
file. When a user starts its video, it triggers a participantJoined
event. Let's call a function named addVideoNode
that will create dynamically a video node element in the html DOM upon receiving this event :
const main = () => {
/* Events handlers */
voxeet.on('participantJoined', (userId, stream) => {
addVideoNode(userId, stream);
});
};
Then open the ui.js
and write the addVideoNode
function :
/*
addVideoNode(userId, stream): void
create a video node element on the video-container <div> for a participant with userId
*/
const addVideoNode = (userId, stream) => {
const videoContainer = document.getElementById('video-container');
var node = document.createElement('video');
node.setAttribute('id', 'video-' + userId);
node.setAttribute('width', 320);
node.setAttribute('height', 240);
videoContainer.appendChild(node);
navigator.attachMediaStream(node, stream);
node.autoplay = 'autoplay';
node.muted = true;
};
This function creates a new node element with id = video-userId
as a child of the video-container
and attaches to it the participant's video stream.
Let's implement video in ViewController.swift.
Layout modification
class ViewController: UIViewController {
...
// Conference UI.
...
var startVideoButton: UIButton!
// Videos views.
var videosView1: VTVideoView!
var videosView2: VTVideoView!
...
override func viewDidLoad() {
...
// Conference delegate.
VoxeetSDK.shared.conference.delegate = self
}
...
}
Interface linking
...
func initConferenceUI() {
...
// Start video button.
startVideoButton = UIButton(type: .system) as UIButton
startVideoButton.frame = CGRect(x: 100, y: leaveButton.frame.origin.y + leaveButton.frame.height + 16, width: 100, height: 30)
startVideoButton.isEnabled = false
startVideoButton.isSelected = true
startVideoButton.setTitle("START VIDEO", for: .normal)
startVideoButton.addTarget(self, action: #selector(startVideoButtonAction), for: .touchUpInside)
self.view.addSubview(startVideoButton)
// Video views.
videosView1 = VTVideoView(frame: CGRect(x: 108, y: startVideoButton.frame.origin.y + startVideoButton.frame.height + 16, width: 84, height: 84))
videosView1.backgroundColor = .black
self.view.addSubview(videosView1)
videosView2 = VTVideoView(frame: CGRect(x: 208, y: startVideoButton.frame.origin.y + startVideoButton.frame.height + 16, width: 84, height: 84))
videosView2.backgroundColor = .black
self.view.addSubview(videosView2)
}
...
@objc func startButtonAction(sender: UIButton!) {
...
// Join the conference with its id.
VoxeetSDK.shared.conference.join(conferenceID: conferenceID, success: { response in
...
self.startVideoButton.isEnabled = true /* Update startVideo button state */
}, fail: { error in })
}, fail: { error in })
}
@objc func leaveButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.leave { error in
...
self.startVideoButton.isEnabled = false /* Update startVideo button state */
}
}
...
Logic implementation
...
@objc func startVideoButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.startVideo { error in
if error == nil {
self.startVideoButton.isEnabled = false
}
}
}
}
extension ViewController: VTConferenceDelegate {
func participantJoined(userID: String, stream: MediaStream) {
participantUpdated(userID: userID, stream: stream)
}
func participantUpdated(userID: String, stream: MediaStream) {
if userID == VoxeetSDK.shared.session.user?.id {
if stream.videoTracks.isEmpty == false {
videosView1.attach(userID: userID, stream: stream)
} else {
videosView1.unattach() /* Optional */
}
} else {
if stream.videoTracks.isEmpty == false {
videosView2.attach(userID: userID, stream: stream)
} else {
videosView2.unattach() /* Optional */
}
}
}
func participantLeft(userID: String) {}
func screenShareStarted(userID: String, stream: MediaStream) {}
func screenShareStopped(userID: String) {}
func messageReceived(userID: String, message: String) {}
}
Layout modification
Edit the main_activity.xml
with the following items :
<LinearLayout ...>
...
<!-- Step 4. Please put below the layout modification with the video step -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/startVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start video" />
<!-- Step 5. Below will be put the block concerning the stop video functionnality -->
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<com.voxeet.sdk.views.VideoView
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<com.voxeet.sdk.views.VideoView
android:id="@+id/videoOther"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
Interface linking
onCreate
modification for MainActivity
@Bind(R.id.video)
protected VideoView video;
@Bind(R.id.videoOther)
protected VideoView videoOther;
Methods for MainActivity
@OnClick(R.id.startVideo)
public void onStartVideo() {
}
private void updateStreams() {
for (User user : VoxeetSdk.conference().getUsers()) {
boolean isLocal = user.getId().equals(VoxeetSdk.session().getUserId());
MediaStream stream = user.streamsHandler().getFirst(MediaStreamType.Camera);
VideoView video = isLocal ? this.video : this.videoOther;
if (null != stream && !stream.videoTracks().isEmpty()) {
video.setVisibility(View.VISIBLE);
video.attach(user.getId(), stream);
}
}
}
Logic implementation
Implementation for onStartVideo
public void onStartVideo() {
VoxeetSdk.conference().startVideo()
.then((result, solver) -> updateViews())
.error(error());
}
Implementation for the related events
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(StreamAddedEvent event) {
updateStreams();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(StreamUpdatedEvent event) {
updateStreams();
}
Stop the video
Layout editing
Open the index.html
file and add a new button named stop-video-btn
in the actions
div
element :
<div id="actions">
<button id="start-video-btn" disabled="true">Start video</button>
<!-- add 'stop-video-btn' just after 'start-video-btn'-->
<button id="stop-video-btn" disabled="true">Stop video</button>
</div>
Interface linking
Open the ui.js
and declare the stopVideoBtn
in the initUI
function
const initUI = () => {
...
const stopVideoBtn = document.getElementById('stop-video-btn');
...
}
Enable the stopVideoBtn
when the user's video has been started. To do so, edit the startVideoBtn
onclick function :
startVideoBtn.onclick = () => {
voxeet.startVideoForUser(voxeet.userId)
.then(() => {
// ------> Enable the button here ------->
stopVideoBtn.disabled = false;
});
};
Then write the onclick handler of the startVideoBtn
:
stopVideoBtn.onclick = () => {
voxeet.stopVideoForUser(voxeet.userId)
.then(() => {
stopVideoBtn.disabled = true;
startVideoBtn.disabled = false;
});
};
Logic implementation
When a user stops its video, it triggers a participantLeft
event. Open the client.js
file and let's call a function named removeVideoNode
that will remove the respective user's video stream from the UI upon receiving this event :
const main = () => {
/* Events handlers */
...
voxeet.on('participantLeft', (userId) => {
removeVideoNode(userId);
});
};
Open the ui.js
and write the removeVideoNode
function :
/*
removeVideoNode(userId): void
remove the video node element of a participant with its userId
*/
const removeVideoNode = (userId) => {
var node = document.getElementById('video-' + userId);
if (node) {
node.parentNode.removeChild(node);
}
};
This function removes a video node element from a specific participant.
Layout modification
class ViewController: UIViewController {
...
// Conference UI.
...
var stopVideoButton: UIButton!
...
}
Interface linking
...
func initConferenceUI() {
...
// Stop video button.
stopVideoButton = UIButton(type: .system) as UIButton
stopVideoButton.frame = CGRect(x: 200, y: leaveButton.frame.origin.y + leaveButton.frame.height + 16, width: 100, height: 30)
stopVideoButton.isEnabled = false
stopVideoButton.isSelected = true
stopVideoButton.setTitle("STOP VIDEO", for: .normal)
stopVideoButton.addTarget(self, action: #selector(stopVideoButtonAction), for: .touchUpInside)
self.view.addSubview(stopVideoButton)
// Video views.
...
}
...
@objc func startButtonAction(sender: UIButton!) {
...
// Join the conference with its id.
VoxeetSDK.shared.conference.join(conferenceID: conferenceID, success: { response in
...
self.stopVideoButton.isEnabled = false /* Update startVideo button state */
}, fail: { error in })
}, fail: { error in })
}
@objc func leaveButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.leave { error in
...
self.stopVideoButton.isEnabled = false /* Update stopVideo button state */
}
}
@objc func startVideoButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.startVideo { error in
if error == nil {
...
self.stopVideoButton.isEnabled = true /* Update stopVideo button state */
}
}
}
...
Logic implementation
...
@objc func stopVideoButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.stopVideo { error in
if error == nil {
self.startVideoButton.isEnabled = true
self.stopVideoButton.isEnabled = false
}
}
}
}
Layout modification
Edit the main_activity.xml
with the following <Button />
after the comment mentionning the stop video functionnality
:
<LinearLayout ...>
...
<LinearLayout ...>
<!-- Step 5. Below will be put the block concerning the stop video functionnality -->
<Button
android:id="@+id/stopVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stop video" />
</LinearLayout>
...
</LinearLayout>
Interface linking
Implementation for onStopVideo
@OnClick(R.id.stopVideo)
public void onStopVideo() {
}
Logic implementation
Implementation for onStopVideo
@OnClick(R.id.stopVideo)
public void onStopVideo() {
VoxeetSdk.conference().stopVideo()
.then((result, solver) -> updateViews())
.error(error());
}
Implementation for the related events
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(StreamRemovedEvent event) {
updateStreams();
}
What's next?
You can now add video to your conference. If you want to learn more about creating video conference applications, move to the next step => Manage participants