Initial commit of Twilio Server Starter for Python
This commit is contained in:
18
.env.example
Normal file
18
.env.example
Normal file
@@ -0,0 +1,18 @@
|
||||
# Required for all uses
|
||||
TWILIO_ACCOUNT_SID=
|
||||
TWILIO_API_KEY=
|
||||
TWILIO_API_SECRET=
|
||||
|
||||
# Required for Video
|
||||
TWILIO_CONFIGURATION_SID=
|
||||
|
||||
# Required for IP Messaging
|
||||
TWILIO_IPM_SERVICE_SID=
|
||||
|
||||
# Required for Notify
|
||||
TWILIO_NOTIFICATION_SERVICE_SID=
|
||||
TWILIO_APN_CREDENTIAL_SID=
|
||||
TWILIO_GCM_CREDENTIAL_SID=
|
||||
|
||||
# Required for Sync
|
||||
TWILIO_SYNC_SERVICE_SID=
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
.env
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
94
README.md
94
README.md
@@ -1,2 +1,92 @@
|
||||
# sdk-starter-python
|
||||
Demo application showcasing Twilio API usage in Python
|
||||
# Twilio SDK Starter Application for Python
|
||||
|
||||
This sample project demonstrates how to use Twilio APIs in a Python web
|
||||
application. Once the app is up and running, check out [the home page](http://localhost:5000)
|
||||
to see which demos you can run. You'll find examples for [IP Messaging](https://www.twilio.com/ip-messaging),
|
||||
[Video](https://www.twilio.com/video), [Sync](https://www.twilio.com/sync), and more.
|
||||
|
||||
Let's get started!
|
||||
|
||||
## Configure the sample application
|
||||
|
||||
To run the application, you'll need to gather your Twilio account credentials and configure them
|
||||
in a file named `.env`. To create this file from an example template, do the following in your
|
||||
Terminal.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Open `.env` in your favorite text editor and configure the following values.
|
||||
|
||||
### Configure account information
|
||||
|
||||
Every sample in the demo requires some basic credentials from your Twilio account. Configure these first.
|
||||
|
||||
| Config Value | Description |
|
||||
| :------------- |:------------- |
|
||||
`TWILIO_ACCOUNT_SID` | Your primary Twilio account identifier - find this [in the console here](https://www.twilio.com/console).
|
||||
`TWILIO_API_KEY` | Used to authenticate - [generate one here](https://www.twilio.com/console/video/dev-tools/api-keys).
|
||||
`TWILIO_API_SECRET` | Used to authenticate - [just like the above, you'll get one here](https://www.twilio.com/console/video/dev-tools/api-keys).
|
||||
|
||||
#### A Note on API Keys
|
||||
|
||||
When you generate an API key pair at the URLs above, your API Secret will only be shown once -
|
||||
make sure to save this information in a secure location, or possibly your `~/.bash_profile`.
|
||||
|
||||
### Configure product-specific settings
|
||||
|
||||
Depending on which demos you'd like to run, you'll need to configure a few more values in your
|
||||
`.env` file.
|
||||
|
||||
| Config Value | Product Demo | Description |
|
||||
| :------------- |:------------- |:------------- |
|
||||
`TWILIO_IPM_SERVICE_SID` | IP Messaging | Like a database for your IP Messaging data - [generate one in the console here](https://www.twilio.com/console/ip-messaging/services)
|
||||
`TWILIO_CONFIGURATION_SID` | Video | Identifier for a set of config properties for your video application - [find yours here](https://www.twilio.com/console/video/profiles)
|
||||
`TWILIO_SYNC_SERVICE_SID` | Sync (Preview) | Like a database for your Sync data - generate one with the curl command below.
|
||||
`TWILIO_NOTIFICATION_SERVICE_SID` | Notify (Preview) | You will need to create a Notify service - [generate one here](https://www.twilio.com/console/notify/services)
|
||||
`TWILIO_APN_CREDENTIAL_SID` | Notify (Preview) | Adds iOS notification ability to your app - [generate one here](https://www.twilio.com/console/notify/credentials). You'll need to provision your APN push credentials to generate this. See [this](https://www.twilio.com/docs/api/ip-messaging/guides/push-notifications-ios) guide on how to do that. (Optional)
|
||||
`TWILIO_GCM_CREDENTIAL_SID` | Notify (Preview) |Adds Android/GCM notification ability to your app - [generate one here](https://www.twilio.com/console/notify/credentials). You'll need to provision your GCM push credentials to generate this. See [this](https://www.twilio.com/docs/api/ip-messaging/guides/push-notifications-android) guide on how to do that (Optional)
|
||||
|
||||
#### Temporary: Generating a Sync Service Instance
|
||||
|
||||
During the Sync developer preview, you will need to generate Sync service
|
||||
instances via API until the Console GUI is available. Using the API key pair you
|
||||
generated above, generate a service instance via REST API with this curl command:
|
||||
|
||||
```bash
|
||||
curl -X POST https://preview.twilio.com/Sync/Services \
|
||||
-d 'FriendlyName=MySyncServiceInstance' \
|
||||
-u 'SKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:your_api_secret'
|
||||
```
|
||||
|
||||
## Run the sample application
|
||||
|
||||
This application uses the lightweight [Flask Framework](http://flask.pocoo.org/).
|
||||
|
||||
We need to set up your Python environment. Install `virtualenv` via `pip`:
|
||||
|
||||
```bash
|
||||
pip install virtualenv
|
||||
```
|
||||
|
||||
Next, we need to install our depenedencies:
|
||||
|
||||
```bash
|
||||
virtualenv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Now we should be all set! Run the application using the `python` command.
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
Your application should now be running at [http://localhost:5000](http://localhost:5000). When you're finished, deactivate your virtual environment using `deactivate`.
|
||||
|
||||

|
||||
|
||||
## License
|
||||
MIT
|
||||
148
app.py
Normal file
148
app.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import os
|
||||
from flask import Flask, jsonify, request
|
||||
from faker import Factory
|
||||
from twilio.rest import Client
|
||||
from twilio.jwt.access_token import AccessToken, SyncGrant, ConversationsGrant, IpMessagingGrant
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
from os.path import join, dirname
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
fake = Factory.create()
|
||||
dotenv_path = join(dirname(__file__), '.env')
|
||||
load_dotenv(dotenv_path)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return app.send_static_file('index.html')
|
||||
|
||||
@app.route('/video/')
|
||||
def video():
|
||||
return app.send_static_file('video/index.html')
|
||||
|
||||
@app.route('/sync/')
|
||||
def sync():
|
||||
return app.send_static_file('sync/index.html')
|
||||
|
||||
@app.route('/notify/')
|
||||
def notify():
|
||||
return app.send_static_file('notify/index.html')
|
||||
|
||||
@app.route('/ipmessaging/')
|
||||
def ipmessaging():
|
||||
return app.send_static_file('ipmessaging/index.html')
|
||||
|
||||
# Basic health check - check environment variables have been configured
|
||||
# correctly
|
||||
@app.route('/config')
|
||||
def config():
|
||||
return jsonify(
|
||||
TWILIO_ACCOUNT_SID=os.environ['TWILIO_ACCOUNT_SID'],
|
||||
TWILIO_NOTIFICATION_SERVICE_SID=os.environ['TWILIO_NOTIFICATION_SERVICE_SID'],
|
||||
TWILIO_APN_CREDENTIAL_SID=os.environ['TWILIO_APN_CREDENTIAL_SID'],
|
||||
TWILIO_GCM_CREDENTIAL_SID=os.environ['TWILIO_GCM_CREDENTIAL_SID'],
|
||||
TWILIO_API_KEY=os.environ['TWILIO_API_KEY'],
|
||||
TWILIO_API_SECRET=bool(os.environ['TWILIO_API_SECRET']),
|
||||
TWILIO_IPM_SERVICE_SID=os.environ['TWILIO_IPM_SERVICE_SID'],
|
||||
TWILIO_SYNC_SERVICE_SID=os.environ['TWILIO_SYNC_SERVICE_SID'],
|
||||
TWILIO_CONFIGURATION_SID=os.environ['TWILIO_CONFIGURATION_SID']
|
||||
)
|
||||
|
||||
@app.route('/token')
|
||||
def token():
|
||||
# get credentials for environment variables
|
||||
account_sid = os.environ['TWILIO_ACCOUNT_SID']
|
||||
api_key = os.environ['TWILIO_API_KEY']
|
||||
api_secret = os.environ['TWILIO_API_SECRET']
|
||||
sync_service_sid = os.environ['TWILIO_SYNC_SERVICE_SID']
|
||||
configuration_profile_sid = os.environ['TWILIO_CONFIGURATION_SID']
|
||||
ipm_service_sid = os.environ['TWILIO_IPM_SERVICE_SID']
|
||||
|
||||
|
||||
# create a randomly generated username for the client
|
||||
identity = fake.user_name()
|
||||
|
||||
# Create a unique endpoint ID for the
|
||||
device_id = request.args.get('device')
|
||||
endpoint = "TwilioAppDemo:{0}:{1}".format(identity, device_id)
|
||||
|
||||
# Create access token with credentials
|
||||
token = AccessToken(account_sid, api_key, api_secret, identity)
|
||||
|
||||
# Create a Sync grant and add to token
|
||||
if sync_service_sid:
|
||||
sync_grant = SyncGrant(endpoint_id=endpoint, service_sid=sync_service_sid)
|
||||
token.add_grant(sync_grant)
|
||||
|
||||
# Create a Video grant and add to token
|
||||
if configuration_profile_sid:
|
||||
video_grant = ConversationsGrant(configuration_profile_sid=configuration_profile_sid)
|
||||
token.add_grant(video_grant)
|
||||
|
||||
# Create an IP Messaging grant and add to token
|
||||
if ipm_service_sid:
|
||||
ipm_grant = IpMessagingGrant(endpoint_id=endpoint, service_sid=ipm_service_sid)
|
||||
token.add_grant(ipm_grant)
|
||||
|
||||
# Return token info as JSON
|
||||
return jsonify(identity=identity, token=token.to_jwt())
|
||||
|
||||
# Notify - create a device binding from a POST HTTP request
|
||||
@app.route('/register', methods=['POST'])
|
||||
def register():
|
||||
# get credentials for environment variables
|
||||
account_sid = os.environ['TWILIO_ACCOUNT_SID']
|
||||
api_key = os.environ['TWILIO_API_KEY']
|
||||
api_secret = os.environ['TWILIO_API_SECRET']
|
||||
service_sid = os.environ['TWILIO_NOTIFICATION_SERVICE_SID']
|
||||
|
||||
# Initialize the Twilio client
|
||||
client = Client(api_key, api_secret, account_sid)
|
||||
|
||||
# Body content
|
||||
content = request.get_json()
|
||||
|
||||
# Get a reference to the notification service
|
||||
service = client.notify.v1.services(service_sid)
|
||||
|
||||
# Create the binding
|
||||
binding = service.bindings.create(
|
||||
endpoint=content["endpoint"],
|
||||
identity=content["identity"],
|
||||
binding_type=content["BindingType"],
|
||||
address=content["Address"])
|
||||
|
||||
print binding
|
||||
|
||||
# Return success message
|
||||
return jsonify(message="Binding created!")
|
||||
|
||||
# Notify - send a notification from a POST HTTP request
|
||||
@app.route('/send-notification', methods=['POST'])
|
||||
def send_notification():
|
||||
# get credentials for environment variables
|
||||
account_sid = os.environ['TWILIO_ACCOUNT_SID']
|
||||
api_key = os.environ['TWILIO_API_KEY']
|
||||
api_secret = os.environ['TWILIO_API_SECRET']
|
||||
service_sid = os.environ['TWILIO_NOTIFICATION_SERVICE_SID']
|
||||
|
||||
# Initialize the Twilio client
|
||||
client = Client(api_key, api_secret, account_sid)
|
||||
|
||||
service = client.notify.v1.services(service_sid)
|
||||
|
||||
# Create a notification for a given identity
|
||||
identity = request.form.get('identity')
|
||||
notification = service.notifications.create(
|
||||
identity=identity,
|
||||
body='Hello ' + identity + '!'
|
||||
)
|
||||
|
||||
return jsonify(message="Notification created!")
|
||||
|
||||
@app.route('/<path:path>')
|
||||
def static_file(path):
|
||||
return app.send_static_file(path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Flask==0.10.1
|
||||
fake-factory==0.5.3
|
||||
twilio==6.0.0rc12
|
||||
python-dotenv==0.6.0
|
||||
72
static/config-check.js
Normal file
72
static/config-check.js
Normal file
@@ -0,0 +1,72 @@
|
||||
$(function() {
|
||||
$.get('/config', function(response) {
|
||||
configureField(response, 'TWILIO_ACCOUNT_SID','twilioAccountSID',false);
|
||||
configureField(response, 'TWILIO_API_KEY','twilioAPIKey',false);
|
||||
configureField(response, 'TWILIO_API_SECRET','twilioAPISecret',true);
|
||||
configureField(response, 'TWILIO_CONFIGURATION_SID','twilioConfigurationSID',false);
|
||||
configureField(response, 'TWILIO_NOTIFICATION_SERVICE_SID','twilioNotificationServiceSID',false);
|
||||
configureField(response, 'TWILIO_APN_CREDENTIAL_SID','twilioAPNCredentialSID',false);
|
||||
configureField(response, 'TWILIO_GCM_CREDENTIAL_SID','twilioGCMCredentialSID',false);
|
||||
configureField(response, 'TWILIO_IPM_SERVICE_SID','twilioIPMServiceSID',false);
|
||||
configureField(response, 'TWILIO_SYNC_SERVICE_SID','twilioSyncServiceSID',false);
|
||||
|
||||
//configure individual product buttons
|
||||
if (response.TWILIO_ACCOUNT_SID && response.TWILIO_ACCOUNT_SID != '' &&
|
||||
response.TWILIO_API_KEY && response.TWILIO_API_KEY != '' && response.TWILIO_API_SECRET) {
|
||||
|
||||
if (response.TWILIO_CONFIGURATION_SID && response.TWILIO_CONFIGURATION_SID != '') {
|
||||
$('#videoDemoButton').addClass('btn-success');
|
||||
} else {
|
||||
$('#videoDemoButton').addClass('btn-danger');
|
||||
}
|
||||
|
||||
if (response.TWILIO_IPM_SERVICE_SID && response.TWILIO_IPM_SERVICE_SID != '') {
|
||||
$('#ipmDemoButton').addClass('btn-success');
|
||||
} else {
|
||||
$('#ipmDemoButton').addClass('btn-danger');
|
||||
}
|
||||
|
||||
if (response.TWILIO_SYNC_SERVICE_SID && response.TWILIO_SYNC_SERVICE_SID != '') {
|
||||
$('#syncDemoButton').addClass('btn-success');
|
||||
} else {
|
||||
$('#syncDemoButton').addClass('btn-danger');
|
||||
}
|
||||
|
||||
if (response.TWILIO_NOTIFICATION_SERVICE_SID && response.TWILIO_NOTIFICATION_SERVICE_SID != '') {
|
||||
$('#notifyDemoButton').addClass('btn-success');
|
||||
} else {
|
||||
$('#notifyDemoButton').addClass('btn-danger');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$('#videoDemoButton').addClass('btn-danger');
|
||||
$('#ipmDemoButton').addClass('btn-danger');
|
||||
$('#syncDemoButton').addClass('btn-danger');
|
||||
$('#notifyDemoButton').addClass('btn-danger');
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
var configureField = function(response, keyName,elementId,masked) {
|
||||
if (masked) {
|
||||
if (response[keyName]) {
|
||||
$('#' + elementId).html('Configured properly');
|
||||
$('#' + elementId).addClass('set');
|
||||
} else {
|
||||
$('#' + elementId).html('Not configured in .env');
|
||||
$('#' + elementId).addClass('unset');
|
||||
}
|
||||
} else {
|
||||
if (response[keyName] && response[keyName] != '') {
|
||||
$('#' + elementId).html(response[keyName]);
|
||||
$('#' + elementId).addClass('set');
|
||||
} else {
|
||||
$('#' + elementId).html('Not configured in .env');
|
||||
$('#' + elementId).addClass('unset');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
7
static/index.css
Normal file
7
static/index.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.config-value.set {
|
||||
color:seagreen;
|
||||
}
|
||||
|
||||
.config-value.unset {
|
||||
color:darkred;
|
||||
}
|
||||
74
static/index.html
Normal file
74
static/index.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Twilio Server Starter Kit</title>
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="index.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Twilio Server Starter Kit Environment Setup</h1>
|
||||
<h2>Account Information</h2>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td class="config-key">TWILIO_ACCOUNT_SID</td>
|
||||
<td class="config-value" id="twilioAccountSID"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-key">TWILIO_API_KEY</td>
|
||||
<td class="config-value" id="twilioAPIKey"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-key">TWILIO_API_SECRET</td>
|
||||
<td class="config-value" id="twilioAPISecret"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Products</h2>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td class="config-product">Video</td>
|
||||
<td class="config-key">TWILIO_CONFIGURATION_SID</td>
|
||||
<td class="config-value" id="twilioConfigurationSID"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-product">Notify</td>
|
||||
<td class="config-key">TWILIO_NOTIFICATION_SERVICE_SID</td>
|
||||
<td class="config-value" id="twilioNotificationServiceSID"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-product">Notify</td>
|
||||
<td class="config-key">TWILIO_APN_CREDENTIAL_SID</td>
|
||||
<td class="config-value" id="twilioAPNCredentialSID"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-product">Notify</td>
|
||||
<td class="config-key">TWILIO_GCM_CREDENTIAL_SID</td>
|
||||
<td class="config-value" id="twilioGCMCredentialSID"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-product">IP Messaging</td>
|
||||
<td class="config-key">TWILIO_IPM_SERVICE_SID</td>
|
||||
<td class="config-value" id="twilioIPMServiceSID"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="config-product">Sync</td>
|
||||
<td class="config-key">TWILIO_SYNC_SERVICE_SID</td>
|
||||
<td class="config-value" id="twilioSyncServiceSID"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Demos</h1>
|
||||
<a id="videoDemoButton" class="btn btn-lg" href="/video/">Video</a>
|
||||
<a id="syncDemoButton" class="btn btn-lg" href="/sync/">Sync</a>
|
||||
<a id="notifyDemoButton" class="btn btn-lg" href="/notify/">Notify</a>
|
||||
<a id="ipmDemoButton" class="btn btn-lg" href="/ipmessaging/">IP Messaging</a>
|
||||
</div> <!-- container -->
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script src="/config-check.js"></script>
|
||||
</body>
|
||||
</script>
|
||||
90
static/ipmessaging/index.css
Executable file
90
static/ipmessaging/index.css
Executable file
@@ -0,0 +1,90 @@
|
||||
* {
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding:0;
|
||||
margin:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
color:#dedede;
|
||||
background-color: #849091;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width:100%;
|
||||
position:absolute;
|
||||
text-align:center;
|
||||
bottom:20px;
|
||||
}
|
||||
|
||||
header a, header a:visited {
|
||||
font-size:18px;
|
||||
color:#dedede;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
section {
|
||||
height:70%;
|
||||
background-color:#2B2B2A;
|
||||
}
|
||||
|
||||
section input {
|
||||
display:block;
|
||||
height:52px;
|
||||
width:800px;
|
||||
margin:10px auto;
|
||||
outline:none;
|
||||
background-color:transparent;
|
||||
border:none;
|
||||
border-bottom:1px solid #2B2B2A;
|
||||
padding:0;
|
||||
font-size:42px;
|
||||
color:#eee;
|
||||
}
|
||||
|
||||
#messages {
|
||||
background-color:#232323;
|
||||
padding:10px;
|
||||
height:100%;
|
||||
width:800px;
|
||||
margin:0 auto;
|
||||
overflow-y:auto;
|
||||
}
|
||||
|
||||
#messages p {
|
||||
margin:5px 0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin:5px 0;
|
||||
font-style:italic;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
margin:5px 0;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.message-container .username {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
font-weight:bold;
|
||||
color:#849091;
|
||||
}
|
||||
|
||||
.me, .username.me {
|
||||
font-weight:bold;
|
||||
color:cyan;
|
||||
}
|
||||
|
||||
.message-container .username.me {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
}
|
||||
27
static/ipmessaging/index.html
Executable file
27
static/ipmessaging/index.html
Executable file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Twilio IP Messaging Quickstart</title>
|
||||
<link rel="shortcut icon" href="//www.twilio.com/marketing/bundles/marketing/img/favicons/favicon.ico">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="https://www.twilio.com/docs/api/ip-messaging/guides/quickstart-js"
|
||||
target="_blank">Read the getting started guide
|
||||
<i class="fa fa-fw fa-external-link"></i>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div id="messages"></div>
|
||||
<input id="chat-input" type="text" placeholder="say anything" autofocus/>
|
||||
</section>
|
||||
|
||||
<script src="//media.twiliocdn.com/sdk/js/common/v0.1/twilio-common.min.js"></script>
|
||||
<script src="//media.twiliocdn.com/sdk/js/ip-messaging/v0.10/twilio-ip-messaging.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
108
static/ipmessaging/index.js
Executable file
108
static/ipmessaging/index.js
Executable file
@@ -0,0 +1,108 @@
|
||||
$(function() {
|
||||
// Get handle to the chat div
|
||||
var $chatWindow = $('#messages');
|
||||
|
||||
// Manages the state of our access token we got from the server
|
||||
var accessManager;
|
||||
|
||||
// Our interface to the IP Messaging service
|
||||
var messagingClient;
|
||||
|
||||
// A handle to the "general" chat channel - the one and only channel we
|
||||
// will have in this sample app
|
||||
var generalChannel;
|
||||
|
||||
// The server will assign the client a random username - store that value
|
||||
// here
|
||||
var username;
|
||||
|
||||
// Helper function to print info messages to the chat window
|
||||
function print(infoMessage, asHtml) {
|
||||
var $msg = $('<div class="info">');
|
||||
if (asHtml) {
|
||||
$msg.html(infoMessage);
|
||||
} else {
|
||||
$msg.text(infoMessage);
|
||||
}
|
||||
$chatWindow.append($msg);
|
||||
}
|
||||
|
||||
// Helper function to print chat message to the chat window
|
||||
function printMessage(fromUser, message) {
|
||||
var $user = $('<span class="username">').text(fromUser + ':');
|
||||
if (fromUser === username) {
|
||||
$user.addClass('me');
|
||||
}
|
||||
var $message = $('<span class="message">').text(message);
|
||||
var $container = $('<div class="message-container">');
|
||||
$container.append($user).append($message);
|
||||
$chatWindow.append($container);
|
||||
$chatWindow.scrollTop($chatWindow[0].scrollHeight);
|
||||
}
|
||||
|
||||
// Alert the user they have been assigned a random username
|
||||
print('Logging in...');
|
||||
|
||||
// Get an access token for the current user, passing a username (identity)
|
||||
// and a device ID - for browser-based apps, we'll always just use the
|
||||
// value "browser"
|
||||
$.getJSON('/token', {
|
||||
device: 'browser'
|
||||
}, function(data) {
|
||||
// Alert the user they have been assigned a random username
|
||||
username = data.identity;
|
||||
print('You have been assigned a random username of: '
|
||||
+ '<span class="me">' + username + '</span>', true);
|
||||
|
||||
// Initialize the IP messaging client
|
||||
accessManager = new Twilio.AccessManager(data.token);
|
||||
messagingClient = new Twilio.IPMessaging.Client(accessManager);
|
||||
|
||||
// Get the general chat channel, which is where all the messages are
|
||||
// sent in this simple application
|
||||
print('Attempting to join "general" chat channel...');
|
||||
var promise = messagingClient.getChannelByUniqueName('general');
|
||||
promise.then(function(channel) {
|
||||
generalChannel = channel;
|
||||
if (!generalChannel) {
|
||||
// If it doesn't exist, let's create it
|
||||
messagingClient.createChannel({
|
||||
uniqueName: 'general',
|
||||
friendlyName: 'General Chat Channel'
|
||||
}).then(function(channel) {
|
||||
console.log('Created general channel:');
|
||||
console.log(channel);
|
||||
generalChannel = channel;
|
||||
setupChannel();
|
||||
});
|
||||
} else {
|
||||
console.log('Found general channel:');
|
||||
console.log(generalChannel);
|
||||
setupChannel();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Set up channel after it has been found
|
||||
function setupChannel() {
|
||||
// Join the general channel
|
||||
generalChannel.join().then(function(channel) {
|
||||
print('Joined channel as '
|
||||
+ '<span class="me">' + username + '</span>.', true);
|
||||
});
|
||||
|
||||
// Listen for new messages sent to the channel
|
||||
generalChannel.on('messageAdded', function(message) {
|
||||
printMessage(message.author, message.body);
|
||||
});
|
||||
}
|
||||
|
||||
// Send a new message to the general channel
|
||||
var $input = $('#chat-input');
|
||||
$input.on('keydown', function(e) {
|
||||
if (e.keyCode == 13) {
|
||||
generalChannel.sendMessage($input.val())
|
||||
$input.val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
35
static/notify/index.html
Normal file
35
static/notify/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello App! - Notify Quickstart</title>
|
||||
<link rel="shortcut icon" href="//www.twilio.com/marketing/bundles/marketing/img/favicons/favicon.ico">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="notify.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="https://www.twilio.com/docs/api/notifications"
|
||||
target="_blank">
|
||||
Read the Twilio Notify guide
|
||||
<i class="fa fa-fw fa-external-link"></i>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h1>Send Notification</h1>
|
||||
<input type="text" id="identityInput" size="30"/>
|
||||
<p/>
|
||||
<input type="submit" id="sendNotificationButton" value="Send Notification"/>
|
||||
|
||||
<div id="message">
|
||||
Welcome to Notify!
|
||||
</div>
|
||||
|
||||
<p>After you set up a notification binding, go ahead and send a notification to that identity!</p>
|
||||
</section>
|
||||
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="notify.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
40
static/notify/notify.css
Normal file
40
static/notify/notify.css
Normal file
@@ -0,0 +1,40 @@
|
||||
* {
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding:0;
|
||||
margin:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
color:#dedede;
|
||||
background-color: #849091;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width:100%;
|
||||
position:absolute;
|
||||
text-align:center;
|
||||
bottom:20px;
|
||||
}
|
||||
|
||||
header a, header a:visited {
|
||||
font-size:18px;
|
||||
color:#dedede;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
section {
|
||||
background-color:#2B2B2A;
|
||||
text-align:center;
|
||||
padding:16px;
|
||||
}
|
||||
|
||||
#message {
|
||||
padding:6px;
|
||||
}
|
||||
12
static/notify/notify.js
Normal file
12
static/notify/notify.js
Normal file
@@ -0,0 +1,12 @@
|
||||
$(function() {
|
||||
|
||||
$('#sendNotificationButton').on('click', function() {
|
||||
$.post('/send-notification', {
|
||||
identity: $('#identityInput').val()
|
||||
}, function(response) {
|
||||
$('#identityInput').val('');
|
||||
$('#message').html(response.message);
|
||||
console.log(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
63
static/sync/index.css
Executable file
63
static/sync/index.css
Executable file
@@ -0,0 +1,63 @@
|
||||
* {
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding:0;
|
||||
margin:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
color:#dedede;
|
||||
background-color: #849091;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width:100%;
|
||||
position:absolute;
|
||||
text-align:center;
|
||||
bottom:20px;
|
||||
}
|
||||
|
||||
header a, header a:visited {
|
||||
font-size:18px;
|
||||
color:#dedede;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
header a:hover {
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
section {
|
||||
background-color:#2B2B2A;
|
||||
text-align:center;
|
||||
padding:16px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
cursor:pointer;
|
||||
background-color:#000;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
#message {
|
||||
padding:6px;
|
||||
}
|
||||
|
||||
#board {
|
||||
width: 33%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#board .board-row {
|
||||
width: 100%;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
#board .board-row button {
|
||||
width: 30%;
|
||||
height: 100px;
|
||||
font-size: 50px;
|
||||
}
|
||||
51
static/sync/index.html
Executable file
51
static/sync/index.html
Executable file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tic-Tac-Twilio - Sync Quickstart</title>
|
||||
<link rel="shortcut icon" href="//www.twilio.com/marketing/bundles/marketing/img/favicons/favicon.ico">
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="https://www.twilio.com/docs/api/sync/quickstart-js"
|
||||
target="_blank">
|
||||
Read the getting started guide
|
||||
<i class="fa fa-fw fa-external-link"></i>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h1>Tic-Tac-Twilio</h1>
|
||||
|
||||
<div id="board">
|
||||
<div class="board-row">
|
||||
<button type="button" data-row="0" data-col="0" disabled> </button>
|
||||
<button type="button" data-row="0" data-col="1" disabled> </button>
|
||||
<button type="button" data-row="0" data-col="2" disabled> </button>
|
||||
</div>
|
||||
<div class="board-row">
|
||||
<button type="button" data-row="1" data-col="0" disabled> </button>
|
||||
<button type="button" data-row="1" data-col="1" disabled> </button>
|
||||
<button type="button" data-row="1" data-col="2" disabled> </button>
|
||||
</div>
|
||||
<div class="board-row">
|
||||
<button type="button" data-row="2" data-col="0" disabled> </button>
|
||||
<button type="button" data-row="2" data-col="1" disabled> </button>
|
||||
<button type="button" data-row="2" data-col="2" disabled> </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="message">
|
||||
Welcome! Initializing Sync...
|
||||
</div>
|
||||
|
||||
<p>Open this page in a few tabs to test!</p>
|
||||
</section>
|
||||
|
||||
|
||||
<script src="//media.twiliocdn.com/sdk/js/sync/v0.3/twilio-sync.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
123
static/sync/index.js
Executable file
123
static/sync/index.js
Executable file
@@ -0,0 +1,123 @@
|
||||
$(function () {
|
||||
//We'll use message to tell the user what's happening
|
||||
var $message = $('#message');
|
||||
|
||||
//Get handle to the game board buttons
|
||||
var $buttons = $('#board .board-row button');
|
||||
|
||||
//Manages the state of our access token we got from the server
|
||||
var accessManager;
|
||||
|
||||
//Our interface to the Sync service
|
||||
var syncClient;
|
||||
|
||||
//We're going to use a single Sync document, our simplest
|
||||
//synchronisation primitive, for this demo
|
||||
var syncDoc;
|
||||
|
||||
//Get an access token for the current user, passing a device ID
|
||||
//In browser-based apps, every tab is like its own unique device
|
||||
//synchronizing state -- so we'll use a random UUID to identify
|
||||
//this tab.
|
||||
$.getJSON('/token', {
|
||||
device: getDeviceId()
|
||||
}, function (tokenResponse) {
|
||||
//Initialize the Sync client
|
||||
syncClient = new Twilio.Sync.Client(tokenResponse.token);
|
||||
|
||||
//Let's pop a message on the screen to show that Sync is ready
|
||||
$message.html('Sync initialized!');
|
||||
|
||||
//Now that Sync is active, lets enable our game board
|
||||
$buttons.attr('disabled', false);
|
||||
|
||||
//This code will create and/or open a Sync document
|
||||
//Note the use of promises
|
||||
syncClient.document('SyncGame').then(function(doc) {
|
||||
//Lets store it in our global variable
|
||||
syncDoc = doc;
|
||||
|
||||
//Initialize game board UI to current state (if it exists)
|
||||
var data = syncDoc.get();
|
||||
if (data.board) {
|
||||
updateUserInterface(data);
|
||||
}
|
||||
|
||||
//Let's subscribe to changes on this document, so when something
|
||||
//changes on this document, we can trigger our UI to update
|
||||
syncDoc.on('updated', updateUserInterface);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//Whenever a board button is clicked:
|
||||
$buttons.on('click', function (e) {
|
||||
//Toggle the value: X, O, or empty
|
||||
toggleCellValue($(e.target));
|
||||
|
||||
//Update the document
|
||||
var data = readGameBoardFromUserInterface();
|
||||
|
||||
//Send updated document to Sync
|
||||
//This should trigger "updated" events on other clients
|
||||
syncDoc.set(data);
|
||||
|
||||
});
|
||||
|
||||
//Toggle the value: X, O, or empty ( for UI)
|
||||
function toggleCellValue($cell) {
|
||||
var cellValue = $cell.html();
|
||||
|
||||
if (cellValue === 'X') {
|
||||
$cell.html('O');
|
||||
} else if (cellValue === 'O') {
|
||||
$cell.html(' ');
|
||||
} else {
|
||||
$cell.html('X');
|
||||
}
|
||||
}
|
||||
|
||||
//Generate random UUID to identify this browser tab
|
||||
//For a more robust solution consider a library like
|
||||
//fingerprintjs2: https://github.com/Valve/fingerprintjs2
|
||||
function getDeviceId() {
|
||||
return 'browser-' +
|
||||
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
//Read the state of the UI and create a new document
|
||||
function readGameBoardFromUserInterface() {
|
||||
var board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
['', '', '']
|
||||
];
|
||||
|
||||
for (var row = 0; row < 3; row++) {
|
||||
for (var col = 0; col < 3; col++) {
|
||||
var selector = '[data-row="' + row + '"]' +
|
||||
'[data-col="' + col + '"]';
|
||||
board[row][col] = $(selector).html().replace(' ', '');
|
||||
}
|
||||
}
|
||||
|
||||
return {board: board};
|
||||
}
|
||||
|
||||
//Update the buttons on the board to match our document
|
||||
function updateUserInterface(data) {
|
||||
for (var row = 0; row < 3; row++) {
|
||||
for (var col = 0; col < 3; col++) {
|
||||
var selector = '[data-row="' + row + '"]' +
|
||||
'[data-col="' + col + '"]';
|
||||
var cellValue = data.board[row][col];
|
||||
$(selector).html(cellValue === '' ? ' ' : cellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
29
static/video/index.html
Executable file
29
static/video/index.html
Executable file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Twilio Video - Video Quickstart</title>
|
||||
<link rel="stylesheet" href="site.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="remote-media"></div>
|
||||
<div id="controls">
|
||||
<div id="preview">
|
||||
<p class="instructions">Hello Beautiful</p>
|
||||
<div id="local-media"></div>
|
||||
<button id="button-preview">Preview My Camera</button>
|
||||
</div>
|
||||
<div id="room-controls">
|
||||
<p class="instructions">Room Name:</p>
|
||||
<input id="room-name" type="text" placeholder="Enter a room name" />
|
||||
<button id="button-join">Join Room</button>
|
||||
<button id="button-leave">Leave Room</button>
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
</div>
|
||||
|
||||
<script src="//media.twiliocdn.com/sdk/js/common/v0.1/twilio-common.min.js"></script>
|
||||
<script src="//media.twiliocdn.com/sdk/js/video/releases/1.0.0-beta2/twilio-video.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="quickstart.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
120
static/video/quickstart.js
Executable file
120
static/video/quickstart.js
Executable file
@@ -0,0 +1,120 @@
|
||||
var videoClient;
|
||||
var activeRoom;
|
||||
var previewMedia;
|
||||
var identity;
|
||||
var roomName;
|
||||
|
||||
// Check for WebRTC
|
||||
if (!navigator.webkitGetUserMedia && !navigator.mozGetUserMedia) {
|
||||
alert('WebRTC is not available in your browser.');
|
||||
}
|
||||
|
||||
// When we are about to transition away from this page, disconnect
|
||||
// from the room, if joined.
|
||||
window.addEventListener('beforeunload', leaveRoomIfJoined);
|
||||
|
||||
$.getJSON('/token', function (data) {
|
||||
identity = data.identity;
|
||||
|
||||
// Create a Conversations Client and connect to Twilio
|
||||
videoClient = new Twilio.Video.Client(data.token);
|
||||
document.getElementById('room-controls').style.display = 'block';
|
||||
|
||||
// Bind button to join room
|
||||
document.getElementById('button-join').onclick = function () {
|
||||
roomName = document.getElementById('room-name').value;
|
||||
if (roomName) {
|
||||
log("Joining room '" + roomName + "'...");
|
||||
|
||||
videoClient.connect({ to: roomName}).then(roomJoined,
|
||||
function(error) {
|
||||
log('Could not connect to Twilio: ' + error.message);
|
||||
});
|
||||
} else {
|
||||
alert('Please enter a room name.');
|
||||
}
|
||||
};
|
||||
|
||||
// Bind button to leave room
|
||||
document.getElementById('button-leave').onclick = function () {
|
||||
log('Leaving room...');
|
||||
activeRoom.disconnect();
|
||||
};
|
||||
});
|
||||
|
||||
// Successfully connected!
|
||||
function roomJoined(room) {
|
||||
activeRoom = room;
|
||||
|
||||
log("Joined as '" + identity + "'");
|
||||
document.getElementById('button-join').style.display = 'none';
|
||||
document.getElementById('button-leave').style.display = 'inline';
|
||||
|
||||
// Draw local video, if not already previewing
|
||||
if (!previewMedia) {
|
||||
room.localParticipant.media.attach('#local-media');
|
||||
}
|
||||
|
||||
room.participants.forEach(function(participant) {
|
||||
log("Already in Room: '" + participant.identity + "'");
|
||||
participant.media.attach('#remote-media');
|
||||
});
|
||||
|
||||
// When a participant joins, draw their video on screen
|
||||
room.on('participantConnected', function (participant) {
|
||||
log("Joining: '" + participant.identity + "'");
|
||||
participant.media.attach('#remote-media');
|
||||
|
||||
participant.on('disconnected', function (participant) {
|
||||
log("Participant '" + participant.identity + "' left the room");
|
||||
});
|
||||
});
|
||||
|
||||
// When a participant disconnects, note in log
|
||||
room.on('participantDisconnected', function (participant) {
|
||||
log("Participant '" + participant.identity + "' left the room");
|
||||
participant.media.detach();
|
||||
});
|
||||
|
||||
// When the conversation ends, stop capturing local video
|
||||
// Also remove media for all remote participants
|
||||
room.on('disconnected', function () {
|
||||
log('Left');
|
||||
room.localParticipant.media.detach();
|
||||
room.participants.forEach(function(participant) {
|
||||
participant.media.detach();
|
||||
});
|
||||
activeRoom = null;
|
||||
document.getElementById('button-join').style.display = 'inline';
|
||||
document.getElementById('button-leave').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Local video preview
|
||||
document.getElementById('button-preview').onclick = function () {
|
||||
if (!previewMedia) {
|
||||
previewMedia = new Twilio.Video.LocalMedia();
|
||||
Twilio.Video.getUserMedia().then(
|
||||
function (mediaStream) {
|
||||
previewMedia.addStream(mediaStream);
|
||||
previewMedia.attach('#local-media');
|
||||
},
|
||||
function (error) {
|
||||
console.error('Unable to access local media', error);
|
||||
log('Unable to access Camera and Microphone');
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// Activity log
|
||||
function log(message) {
|
||||
var logDiv = document.getElementById('log');
|
||||
logDiv.innerHTML += '<p>> ' + message + '</p>';
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function leaveRoomIfJoined() {
|
||||
if (activeRoom) {
|
||||
activeRoom.disconnect();
|
||||
}
|
||||
}
|
||||
144
static/video/site.css
Executable file
144
static/video/site.css
Executable file
@@ -0,0 +1,144 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Share+Tech+Mono);
|
||||
|
||||
body,
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #272726;
|
||||
}
|
||||
|
||||
div#remote-media {
|
||||
height: 43%;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
div#remote-media video {
|
||||
border: 1px solid #272726;
|
||||
margin: 3em 2em;
|
||||
height: 70%;
|
||||
max-width: 27% !important;
|
||||
background-color: #272726;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div#controls {
|
||||
padding: 3em;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
div#controls div {
|
||||
float: left;
|
||||
}
|
||||
|
||||
div#controls div#room-controls,
|
||||
div#controls div#preview {
|
||||
width: 16em;
|
||||
margin: 0 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div#controls p.instructions {
|
||||
text-align: left;
|
||||
margin-bottom: 1em;
|
||||
font-family: Helvetica-LightOblique, Helvetica, sans-serif;
|
||||
font-style: oblique;
|
||||
font-size: 1.25em;
|
||||
color: #777776;
|
||||
}
|
||||
|
||||
div#controls button {
|
||||
width: 15em;
|
||||
height: 2.5em;
|
||||
margin-top: 1.75em;
|
||||
border-radius: 1em;
|
||||
font-family: "Helvetica Light", Helvetica, sans-serif;
|
||||
font-size: .8em;
|
||||
font-weight: lighter;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
div#controls div#room-controls input {
|
||||
font-family: Helvetica-LightOblique, Helvetica, sans-serif;
|
||||
font-style: oblique;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div#controls button:active {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
div#controls div#preview div#local-media {
|
||||
width: 270px;
|
||||
height: 202px;
|
||||
border: 1px solid #cececc;
|
||||
box-sizing: border-box;
|
||||
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjgwcHgiIGhlaWdodD0iODBweCIgdmlld0JveD0iMCAwIDgwIDgwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjEgKDEyMDAyKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5GaWxsIDUxICsgRmlsbCA1MjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPgogICAgICAgIDxnIGlkPSJjdW1tYWNrIiBza2V0Y2g6dHlwZT0iTVNMYXllckdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTU5LjAwMDAwMCwgLTE3NDYuMDAwMDAwKSIgZmlsbD0iI0ZGRkZGRiI+CiAgICAgICAgICAgIDxnIGlkPSJGaWxsLTUxLSstRmlsbC01MiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTU5LjAwMDAwMCwgMTc0Ni4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0zOS42ODYsMC43MyBDMTcuODUsMC43MyAwLjA4NSwxOC41IDAuMDg1LDQwLjMzIEMwLjA4NSw2Mi4xNyAxNy44NSw3OS45MyAzOS42ODYsNzkuOTMgQzYxLjUyMiw3OS45MyA3OS4yODcsNjIuMTcgNzkuMjg3LDQwLjMzIEM3OS4yODcsMTguNSA2MS41MjIsMC43MyAzOS42ODYsMC43MyBMMzkuNjg2LDAuNzMgWiBNMzkuNjg2LDEuNzMgQzYxLjAwNSwxLjczIDc4LjI4NywxOS4wMiA3OC4yODcsNDAuMzMgQzc4LjI4Nyw2MS42NSA2MS4wMDUsNzguOTMgMzkuNjg2LDc4LjkzIEMxOC4zNjcsNzguOTMgMS4wODUsNjEuNjUgMS4wODUsNDAuMzMgQzEuMDg1LDE5LjAyIDE4LjM2NywxLjczIDM5LjY4NiwxLjczIEwzOS42ODYsMS43MyBaIiBpZD0iRmlsbC01MSI+PC9wYXRoPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTQ3Ljk2LDUzLjMzNSBMNDcuOTYsNTIuODM1IEwyMC4wOTMsNTIuODM1IEwyMC4wOTMsMjcuODI1IEw0Ny40NiwyNy44MjUgTDQ3LjQ2LDM4LjI1NSBMNTkuMjc5LDMwLjgwNSBMNTkuMjc5LDQ5Ljg1NSBMNDcuNDYsNDIuNDA1IEw0Ny40Niw1My4zMzUgTDQ3Ljk2LDUzLjMzNSBMNDcuOTYsNTIuODM1IEw0Ny45Niw1My4zMzUgTDQ4LjQ2LDUzLjMzNSBMNDguNDYsNDQuMjE1IEw2MC4yNzksNTEuNjY1IEw2MC4yNzksMjguOTk1IEw0OC40NiwzNi40NDUgTDQ4LjQ2LDI2LjgyNSBMMTkuMDkzLDI2LjgyNSBMMTkuMDkzLDUzLjgzNSBMNDguNDYsNTMuODM1IEw0OC40Niw1My4zMzUgTDQ3Ljk2LDUzLjMzNSIgaWQ9IkZpbGwtNTIiPjwvcGF0aD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
div#controls div#preview div#local-media video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#controls div#preview button#button-preview {
|
||||
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE3cHgiIGhlaWdodD0iMTJweCIgdmlld0JveD0iMCAwIDE3IDEyIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjEgKDEyMDAyKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5GaWxsIDM0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9ImN1bW1hY2siIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjUuMDAwMDAwLCAtMTkwOS4wMDAwMDApIiBmaWxsPSIjMEEwQjA5Ij4KICAgICAgICAgICAgPHBhdGggZD0iTTEzNi40NzEsMTkxOS44NyBMMTM2LjQ3MSwxOTE5LjYyIEwxMjUuNzY3LDE5MTkuNjIgTDEyNS43NjcsMTkxMC4wOCBMMTM2LjIyMSwxOTEwLjA4IEwxMzYuMjIxLDE5MTQuMTUgTDE0MC43ODUsMTkxMS4yNyBMMTQwLjc4NSwxOTE4LjQyIEwxMzYuMjIxLDE5MTUuNTUgTDEzNi4yMjEsMTkxOS44NyBMMTM2LjQ3MSwxOTE5Ljg3IEwxMzYuNDcxLDE5MTkuNjIgTDEzNi40NzEsMTkxOS44NyBMMTM2LjcyMSwxOTE5Ljg3IEwxMzYuNzIxLDE5MTYuNDUgTDE0MS4yODUsMTkxOS4zMyBMMTQxLjI4NSwxOTEwLjM3IEwxMzYuNzIxLDE5MTMuMjQgTDEzNi43MjEsMTkwOS41OCBMMTI1LjI2NywxOTA5LjU4IEwxMjUuMjY3LDE5MjAuMTIgTDEzNi43MjEsMTkyMC4xMiBMMTM2LjcyMSwxOTE5Ljg3IEwxMzYuNDcxLDE5MTkuODciIGlkPSJGaWxsLTM0IiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=)1em center no-repeat #fff;
|
||||
border: none;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
div#controls div#log {
|
||||
border: 1px solid #686865;
|
||||
}
|
||||
|
||||
div#controls div#room-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#controls div#room-controls input {
|
||||
width: 100%;
|
||||
height: 2.5em;
|
||||
padding: .5em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
div#controls div#room-controls button {
|
||||
color: #fff;
|
||||
background: 0 0;
|
||||
border: 1px solid #686865;
|
||||
}
|
||||
|
||||
div#controls div#room-controls button#button-leave {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#controls div#log {
|
||||
width: 35%;
|
||||
height: 9.5em;
|
||||
margin-top: 2.75em;
|
||||
text-align: left;
|
||||
padding: 1.5em;
|
||||
float: right;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
div#controls div#log p {
|
||||
color: #686865;
|
||||
font-family: 'Share Tech Mono', 'Courier New', Courier, fixed-width;
|
||||
font-size: 1.25em;
|
||||
line-height: 1.25em;
|
||||
margin-left: 1em;
|
||||
text-indent: -1.25em;
|
||||
width: 90%;
|
||||
}
|
||||
Reference in New Issue
Block a user