$ mkdir webrtc-data-track $ cd webrtc-data-track
$ mkdir static $ mkdir templates
TWILIO_ACCOUNT_SID=<your-twilio-account-sid>
TWILIO_ACCOUNT_SID=<your-twilio-account-sid> TWILIO_API_KEY_SID=<your-twilio-api-key-sid> TWILIO_API_KEY_SECRET=<your-twilio-api-key-secret>
$ python -m venv venv $ source venv/bin/activate (venv) $ pip install twilio flask python-dotenv
$ python -m venv venv $ venv\Scripts\activate (venv) $ pip install twilio flask python-dotenv
pip
, the Python package installer, to install the three Python packages that you are going to use in this project, which are:certifi==2020.4.5.1 chardet==3.0.4 click==7.1.1 Flask==1.1.2 idna==2.9 itsdangerous==1.1.0 Jinja2==2.11.2 MarkupSafe==1.1.1 PyJWT==1.7.1 python-dotenv==0.12.0 pytz==2019.3 requests==2.23.0 six==1.14.0 twilio==6.38.1 urllib3==1.25.8 Werkzeug==1.0.1
import os import time from dotenv import load_dotenv from flask import Flask, render_template from twilio.jwt.access_token import AccessToken from twilio.jwt.access_token.grants import VideoGrant load_dotenv() twilio_account_sid = os.environ.get('TWILIO_ACCOUNT_SID') twilio_api_key_sid = os.environ.get('TWILIO_API_KEY_SID') twilio_api_key_secret = os.environ.get('TWILIO_API_KEY_SECRET') app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/token', methods=['POST']) def token(): username = f'user-{time.time()}' # generate access token token = AccessToken(twilio_account_sid, twilio_api_key_sid, twilio_api_key_secret, identity=username, ttl=3600) # add grants to token token.add_grant(VideoGrant(room='ping-room')) # return token return {'token': token.to_jwt().decode()}
ttl=3600
argument, which gives the validity time of the token in seconds. The longest allowed duration for a token is 24 hours.ping-room
. For this example this is sufficient, but a more complex application could use many different rooms and assign clients to different rooms to receive different notifications.(venv) $ FLASK_ENV=development flask run
(venv) $ set FLASK_ENV=development (venv) $ flask run
* Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 274-913-316
render_template()
function is called.<html> <head> <title>WebRTC Data Track Demo</title> </head> <body> <h1>WebRTC Data Track Demo</h1> <p id="message">Connecting...</p> <div> <label for="name">Name:</label> <input id="name"> <button id="ping">Ping!</button> </div> <ul id="pings"></ul> <script src="https://media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script> <script src="{{ url_for('static', filename='ping.js') }}"></script> </body> </html>
<ul>
element at the end, where pings from other instances of the application will be dynamically added.connect()
function that connects the client to the Twilio Programmable Video service. This involves a few steps:connect()
, along with an auxiliary function setMessage()
. Copy the code below to your ping.js file:const dataTrack = new Twilio.Video.LocalDataTrack(); const setMessage = (message) => { document.getElementById('message').innerHTML = message; }; const connect = async () => { // get a token let res = await fetch('/token', {method: 'POST'}); let data = await res.json(); // connect to the room let room; try { room = await Twilio.Video.connect(data.token, {tracks: [dataTrack]}); setMessage('Connected!'); } catch { setMessage('Connection Error'); return; } // register to receive events from other participants already in the room room.participants.forEach(participantConnected); // register for new participants when they join the room room.on('participantConnected', participantConnected); }
dataTrack
constant is initialized with a local data track object, from the twilio-video.js library. This is the object that represents the data track that sends data to other participants in the room.setMessage()
function is a short auxiliary function that modifies the text in the <p id=”message”>
. As you have seen, this <p>
element is initialized with the text Connecting…
. With this function, the text can be changed to either Connected!
or Connection Error
depending on the results of the connection attempt.connect()
function starts by sending a POST
request to the /token endpoint in the Flask backend. The response is a JSON payload with the format {“token”: “access-token-here”}
.data
local variable.VideoGrant
object defined by the Python back end.Twilio.Video.connect()
function is designed to start local video and audio tracks, so for this application the tracks
option is added to override the default and just connect the local data track created at the beginning. The message in the page is updated with the success or failure of this connection.participantJoined()
that you will write in the next section.participantConnected()
function that you see below calls the trackSubscribed()
handler for each existing track. It also sets this function up as a handler in case a track is published later on. The trackSubscribed()
function checks if the track is a data track. If it is, the function adds a handler for the message
event on the track, which will trigger on incoming data sent by the remote participant.const participantConnected = (participant) => { participant.tracks.forEach(publication => { if (publication.track) { trackSubscribed(publication.track); } }); participant.on('trackSubscribed', trackSubscribed); }; const trackSubscribed = (track) => { if (track.kind === 'data') { track.on('message', data => receivePing(data)); } };
sendPing()
function to ping.js that sends the contents of the text input box over the local data track:const sendPing = () => { dataTrack.send(document.getElementById('name').value) }
receivePing()
function that appends the received text to the <ul id=”pings”>
element as an additional <li>
element:const receivePing = (name) => { document.getElementById('pings').innerHTML += `<li>Ping from ${name}!</li>` }
sendPing()
function as a handler for the click
event on the button, and call the connect()
function to initiate a connection:document.getElementById('ping').addEventListener('click', () => sendPing()); connect();
message
event is received). It isn’t really necessary that the application performs both functions, depending on the case an instance of the application can be just a publisher, or just a subscriber, and when it is a subscriber it can choose which room(s) to subscribe to. This can make for a very powerful pub/sub system with multiple channels.