Integrate Video
This tutorial is a part of a series; it comes after Create a basic audio conference application.
This tutorial guides how to add video features to a conference.
Start the video
- JavaScript
- Swift
- Java
Layout editing
Open the index.html
file and create a div
with the id video-container
, to which the video stream will be dynamically added. Also add a new element with the id actions
that will contain a button element with the id start-video-btn
:
<div id="app">
<div id="form">...</div>
<div id="actions">
<button id="start-video-btn" disabled>Start video</button>
</div>
<div id="video-container"></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 to enable the startVideoBtn
when the user joins the conference:
joinButton.onclick = () => {
... => VoxeetSDK.conference.join(conference, {}))
.then(() => {
// -----> Enable the button here ------>
startVideoBtn.disabled = false
})
...
}
Then, write the startVideoBtn
onclick handler:
startVideoBtn.onclick = () => {
VoxeetSDK.conference.startVideo(VoxeetSDK.session.participant).then(() => {
startVideoBtn.disabled = true
})
}
Logic implementation
Open the ui.js
file and write the addVideoNode
function that will dynamically create a video node element in the html DOM:
/*
addVideoNode(userId, stream): void
create a video node element on the video-container <div> for a participant with userId
*/
const addVideoNode = (participant, stream) => {
let videoNode = document.getElementById('video-' + participant.id);
if (!videoNode) {
videoNode = document.createElement('video');
videoNode.setAttribute('id', 'video-' + participant.id);
videoNode.setAttribute('height', 240);
videoNode.setAttribute('width', 320);
videoNode.setAttribute("playsinline", true);
videoNode.setAttribute("muted", true);
videoNode.setAttribute("autoplay", 'autoplay');
const videoContainer = document.getElementById('video-container');
videoContainer.appendChild(videoNode);
}
navigator.attachMediaStream(videoNode, stream);
};
When a participant joins a conference with audio and/or video enabled, an event named streamAdded
is emitted to all other participants.
Note: The playsinline
attribute used in the example prevents the video from freezing in the Safari browser.
Open the client.js
file and call addVideoNode
upon receiving the streamAdded
and streamUpdated
events:
const main = () => {
/* Events handlers */
VoxeetSDK.conference.on('streamAdded', (participant, stream) => {
if (stream.type === 'ScreenShare') return;
if (stream.getVideoTracks().length) {
addVideoNode(participant, stream);
}
});
VoxeetSDK.conference.on('streamUpdated', (participant, stream) => {
if (stream.type === 'ScreenShare') return;
if (stream.getVideoTracks().length) {
addVideoNode(participant, stream);
} else {
removeVideoNode(participant);
}
});
};
Add UI for starting video in ViewController.swift
.
1. In ViewController.swift, add some variables to the ViewController
class to refer to the user interface elements that will be created in step 2.
Set the ViewContoller
instance as the conference delegate. The delegate will be called when video streams are added to the conference.
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
}
...
}
2. Modify initConferenceUI
to extend the user interface and enable and disable the startVideoButton
as appropriate.
...
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(conference: conference, 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 */
}
}
...
3. Add a method to start video.
...
@objc func startVideoButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.startVideo { error in
if error == nil {
self.startVideoButton.isEnabled = false
}
}
}
4. Extend ViewController
as a VTConferenceDelegate
. This allows the controller to act when video streams are added or removed from the conference.
extension ViewController: VTConferenceDelegate {
func streamAdded(participant: VTParticipant, stream: MediaStream) {
streamUpdated(participant: participant, stream: stream)
}
func streamUpdated(participant: VTParticipant, stream: MediaStream) {
switch stream.type {
case .Camera:
if participant.id == VoxeetSDK.shared.session.participant?.id {
if !stream.videoTracks.isEmpty {
videosView1.attach(participant: participant, stream: stream)
} else {
videosView1.unattach() /* Optional */
}
} else {
if !stream.videoTracks.isEmpty {
videosView2.attach(participant: participant, stream: stream)
} else {
videosView2.unattach() /* Optional */
}
}
case .ScreenShare: break
default: break
}
}
func streamRemoved(participant: VTParticipant, stream: MediaStream) {}
func statusUpdated(status: VTConferenceStatus) {}
}
Layout modification
Edit the main_activity.xml
with the following items :
<LinearLayout ...>
...
<!-- Step 3. Put the layout changes for the video step here -->
<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 3.2. This layout will be upgraded in the stop video step -->
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<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>
<!-- Step 4. ...
</LinearLayout>
2. Modify the interface linking in the MainActivity
class in MainActivity.java
:
- New fields for
MainActivity
:
@Bind(R.id.video)
protected VideoView video;
@Bind(R.id.videoOther)
protected VideoView videoOther;
- New methods for
MainActivity
:
@OnClick(R.id.startVideo)
public void onStartVideo() {
}
private void updateStreams() {
}
- Add the following code to the
onCreate
method:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//adding the startVideo in the flow
add(views, R.id.startVideo);
add(buttonsInConference, R.id.startVideo);
add(buttonsNotInOwnVideo, R.id.startVideo);
}
3. Add the following logic to the application:
- In
updateViews
enable and disable buttons based on the own video state.
private void updateViews() {
...
if (null != current) {
if (current.isOwnVideoStarted()) {
setEnabled(buttonsInOwnVideo, true);
setEnabled(buttonsNotInOwnVideo, false);
} else {
setEnabled(buttonsInOwnVideo, false);
setEnabled(buttonsNotInOwnVideo, true);
}
}
}
- Use the following implementation for
onStartVideo
:
public void onStartVideo() {
VoxeetSDK.conference().startVideo()
.then((result, solver) -> updateViews())
.error(error());
}
- Use the following implementation for
updateStreams
:
private void updateStreams() {
for (Participant user : VoxeetSDK.conference().getParticipants()) {
boolean isLocal = user.getId().equals(VoxeetSDK.session().getParticipantId());
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);
}
}
}
- Add handlers for
StreamAddedEvent
andStreamUpdatedEvent
.
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(StreamAddedEvent event) {
updateStreams();
updateViews();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(StreamUpdatedEvent event) {
updateStreams();
updateViews();
}
Stop the video
- JavaScript
- Swift
- Java
Layout editing
Open the index.html
file and add a new button
with the id stop-video-btn
in the actions
div
element:
<div id="actions">
<button id="start-video-btn" disabled>Start video</button>
<!-- add 'stop-video-btn' just after 'start-video-btn'-->
<button id="stop-video-btn" disabled>Stop video</button>
</div>
Interface linking
Open the ui.js
file and declare the stopVideoBtn
in the initUI
function:
const initUI = () => {
...
const stopVideoBtn = document.getElementById('stop-video-btn')
...
}
Edit the startVideoBtn
onclick function to enable the stopVideoBtn
when the user’s video is started:
startVideoBtn.onclick = () => {
VoxeetSDK.conference.startVideo(VoxeetSDK.session.participant).then(() => {
// ------> Enable the button here ------->
stopVideoBtn.disabled = false
})
}
Then, write the onclick handler of the stopVideoBtn
:
stopVideoBtn.onclick = () => {
VoxeetSDK.conference.stopVideo(VoxeetSDK.session.participant).then(() => {
stopVideoBtn.disabled = true
startVideoBtn.disabled = false
})
}
Logic implementation
Open the ui.js
file and write the removeVideoNode
function that removes the specific video element from the UI:
const removeVideoNode = participant => {
let videoNode = document.getElementById("video-" + participant.id)
if (videoNode) {
videoNode.parentNode.removeChild(videoNode)
}
}
When a participant leaves the conference, an event named streamRemoved
is emitted to all other participants.
Open client.js
file and call removeVideoNode
upon receiving the streamRemoved
event:
const main = () => {
/* Events handlers */
...
VoxeetSDK.conference.on('streamRemoved', (participant, stream) => {
removeVideoNode(participant)
})
}
Add UI for stopping video in ViewController.swift
.
1. In ViewController.swift, add a variable to the ViewController
class to refer to the user interface element that will be created in step 2.
class ViewController: UIViewController {
...
// Conference UI.
...
var stopVideoButton: UIButton!
...
}
2. Modify initConferenceUI
to extend the user interface and enable & disable the stopVideoButton
as appropriate.
...
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 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 */
}
}
}
...
3. Add a method to stop the video.
...
@objc func stopVideoButtonAction(sender: UIButton!) {
VoxeetSDK.shared.conference.stopVideo { error in
if error == nil {
self.startVideoButton.isEnabled = true
self.stopVideoButton.isEnabled = false
}
}
}
1. To modify the layout, edit the main_activity.xml
file, adding the following content for Step 3.2:
<LinearLayout ...>
...
<LinearLayout ...>
...
<!-- Step 3.2. This layout will be upgraded in the stop video step -->
<Button
android:id="@+id/stopVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stop video" />
</LinearLayout>
...
</LinearLayout>
2. Modify the interface linking in the MainActivity
class in MainActivity.java
:
- New method for
MainActivity
:
@OnClick(R.id.stopVideo)
public void onStopVideo() {
}
- Add the following code to the
onCreate
method:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//adding the stopVideo in the flow
add(views, R.id.stopVideo);
add(buttonsInConference, R.id.stopVideo);
add(buttonsInOwnVideo, R.id.stopVideo);
}
3. Add the following logic to the application:
- Use the following implementation for
onLogin
:
@OnClick(R.id.stopVideo)
public void onStopVideo() {
VoxeetSDK.conference().stopVideo()
.then((result, solver) -> updateViews())
.error(error());
}
- Add a handler for the
StreamRemovedEvent
:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(StreamRemovedEvent event) {
updateStreams();
updateViews();
}
What’s next?
If you want to learn more about creating conference applications, go to the Manage participants tutorial.