Repository: opentok/one-to-one-sample-apps
Branch: master
Commit: 4123c6ae17b7
Files: 80
Total size: 234.8 KB
Directory structure:
gitextract_smhvk9sm/
├── .github/
│ ├── CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── LICENSE
├── README.md
├── android/
│ ├── .gitignore
│ ├── OneToOneSample/
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle
│ │ │ ├── proguard-rules.pro
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── tokbox/
│ │ │ │ └── android/
│ │ │ │ └── onetoonesample/
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── config/
│ │ │ │ │ └── OpenTokConfig.java
│ │ │ │ └── ui/
│ │ │ │ ├── PreviewCameraFragment.java
│ │ │ │ ├── PreviewControlFragment.java
│ │ │ │ └── RemoteControlFragment.java
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ ├── bckg_audio_only.xml
│ │ │ │ ├── bckg_icon.xml
│ │ │ │ ├── end_call_button.xml
│ │ │ │ ├── gradient_audionly.xml
│ │ │ │ ├── gradient_backg.xml
│ │ │ │ ├── initiate_call_button.xml
│ │ │ │ └── preview.xml
│ │ │ ├── layout/
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── preview_actionbar_fragment.xml
│ │ │ │ ├── preview_camera_fragment.xml
│ │ │ │ └── remote_actionbar_fragment.xml
│ │ │ ├── values/
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21/
│ │ │ │ └── styles.xml
│ │ │ └── values-w820dp/
│ │ │ └── dimens.xml
│ │ ├── build.gradle
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ ├── gradlew
│ │ ├── gradlew.bat
│ │ └── settings.gradle
│ └── README.md
├── iOS/
│ ├── .gitignore
│ ├── OneToOneSample.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── OneToOneSample.xcscheme
│ ├── Podfile
│ ├── README.md
│ └── SampleApp/
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets/
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── audio.imageset/
│ │ │ └── Contents.json
│ │ ├── hangUp.imageset/
│ │ │ └── Contents.json
│ │ ├── mic.imageset/
│ │ │ └── Contents.json
│ │ ├── mutedMic.imageset/
│ │ │ └── Contents.json
│ │ ├── noAudio.imageset/
│ │ │ └── Contents.json
│ │ ├── noVideo.imageset/
│ │ │ └── Contents.json
│ │ ├── reverse cameras.imageset/
│ │ │ └── Contents.json
│ │ ├── startCall.imageset/
│ │ │ └── Contents.json
│ │ └── video.imageset/
│ │ └── Contents.json
│ ├── Base.lproj/
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── MainView.h
│ ├── MainView.m
│ ├── MainViewController.h
│ ├── MainViewController.m
│ ├── UIView+Helper.h
│ ├── UIView+Helper.m
│ └── main.m
└── js/
├── .eslintrc.json
├── .gitignore
├── .jsbeautifyrc
├── Procfile
├── README.md
├── package.json
├── public/
│ ├── css/
│ │ └── style.css
│ ├── index.html
│ └── js/
│ ├── app.js
│ └── components/
│ └── opentok-acc-core.js
└── server.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CONDUCT.md
================================================
**opentok/one-to-one-sample-apps** Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to OpenTok One-to-One Communication Sample Apps
## Code of Conduct
Please read our [Code of Conduct](https://github.com/opentok/one-to-one-sample-apps/blob/master/.github/CONDUCT.md). Intolerance, disrespect, and any of form of negativity will not be tolerated.
## Opening a new issue
1. Read *the entire* [README](https://github.com/opentok/one-to-one-sample-apps/blob/master/README.md).
* Search [open issues](https://github.com/opentok/one-to-one-sample-apps/issues) *and* [closed issues](https://github.com/opentok/one-to-one-sample-apps/issues?q=is%3Aissue+is%3Aclosed) to **avoid opening a duplicate issue.
* If your issue exists and you have some new information to contribute, you may add a comment to its thread.
* Otherwise, open a new issue with a clear title and description.
* Provide **all** of the following information:
- Library version(s)
- iOS, Android, or browser version(s)
- Devices, simulators, or machines affected
- Expected behavior vs actual behavior
- Complete steps to reproduce the issue
- Link to a project that exhibits the issue. It is recommended that you fork the repo and modify the demo project.
- Screenshots, GIFs, or videos depicting the issue, if applicable.
- Full crash log, if applicable.
- A list of all possibly related issues.
## Submitting a pull request
1. Link to the issue that the pull request resolves. If the issue does not exist, create one.
2. Write unit tests that test your changes, if applicable.
3. Update header documentation as needed.
4. Follow the existing coding style. For more information, see [style guidelines](https://github.com/NYTimes/objective-c-style-guide).
5. Resolve any merge conflicts.
6. Squash your commits into a single commit.
## Did you read all of this?
Be sure you have visited all the links in this document.
### New issue checklist
When opening your new issue and filling out the checklist, you'll be asked for confirmation. Confirm that you've read this with these emoji: :muscle::sunglasses::facepunch:
> - [x] I have reviewed the contributing guidelines. Confirmation: :muscle::sunglasses::facepunch:
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
## New issue checklist
- [ ] I have read all of the [`README`](https://github.com/opentok/one-to-one-sample-apps/blob/master/README.md)
- [ ] I have searched [existing issues](https://github.com/opentok/one-to-one-sample-apps/issues?q=is%3Aissue+sort%3Acreated-desc) and **this is not a duplicate**.
### General information
- Library version(s):
- iOS/Android/Browser version(s):
- Devices/Simulators/Machine affected:
- Reproducible in the demo project? (Yes/No):
- Related issues:
## Bug report
#### Expected behavior
> ...
#### Actual behavior
> ...
#### Steps to reproduce
> ...
#### Crash log? Screenshots? Videos? Sample project?
>...
## Question or Feature Request
> ...
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Pull request checklist
- [ ] All tests pass. Demo project builds and runs.
- [ ] I have resolved any merge conflicts. Confirmation: ____
#### This fixes issue #___.
## What's in this pull request?
>...
================================================
FILE: LICENSE
================================================
LICENSE
The MIT License (MIT)
Copyright (c) 2016 TokBox, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# DEPRECATED: OpenTok One-to-One Communication Sample App
Version 1.3
> This repository has been deprecated and is superseded by the following repositories:
>
> [accelerator-sample-apps-js](https://github.com/opentok/accelerator-sample-apps-js)
>
> [accelerator-sample-apps-android](https://github.com/opentok/accelerator-sample-apps-android)
>
> [accelerator-sample-apps-ios](https://github.com/opentok/accelerator-sample-apps-ios)
---
The OpenTok One-to-One Communication Sample App is an open-source solution that enables you to quickly get started in your development efforts to set up interoperable, production-quality audio/video communication between users.
With this sample app, you can:
- Start and end audio/visual communication between two users.
- Achieve interoperability between web and mobile devices.
- Mute or unmute audio.
- Enable or disable video.
- Control the camera to point in the forward direction or in the reverse direction (selfie mode).
- Customize the UI features and layout.
You can create mobile apps for Android and iOS, or embed the interactive session between users into any website. To get started with your development, visit the following sites:
- [OpenTok One-to-One Communication Sample App for Android](./android)
- [OpenTok One-to-One Communication Sample App for iOS](./iOS)
- [OpenTok One-to-One Communication Sample App for JavaScript](./js)
# Device interoperability with One-to-One communication
The OpenTok One-to-One Communication Sample App highlights the interoperability of web and mobile devices using the OpenTok platform. Regardless of the supported devices used, the OpenTok platform supports the ability of users to interact with each other and exchange audio and video. Even if the clients are on different platforms, they can both connect, publish, and subscribe to streams in the same session.
This sample app requires a **Session ID**, **Token**, and **API Key**. In the sample, you can get these values at the [OpenTok Developer Dashboard](https://dashboard.tokbox.com/). For production deployment, you must generate the **Session ID** and **Token** values using one of the [OpenTok Server SDKs](https://tokbox.com/developer/sdks/server/).
For example, suppose one user is using a web (JS) version of the One-to-One Communication Sample App and another user is using a mobile version (Android or iOS). If they are both using the same **Session ID** and **API Key**, they can subscribe to each other’s audio and video streams, and the user interface rendered on both devices will allow them to interact with each other and take advantage of all the features of the sample app.
Use the following approach to try this out:
1. Configure a web and mobile user with the required **Session ID**, **Token**, and **API Key** values, using the same **Session ID** and **API Key** for each.
2. Start the web and mobile apps. You will observe the following interactions:
- Both apps connect to the session.
- Both apps start publishing and subscribing to each other’s streams.
3. Observe what happens for each user when you:
- Enable or disable local audio/video on the mobile app.
- Enable or disable local audio/video on the web app.
- Enable or disable remote audio/video on the mobile app.
- Enable or disable remote audio/video on the web app.
As you get started with this OpenTok sample, you will learn the best practices used to develop and manage the audio, video, and camera elements on mobile devices or in the browser. We recommend this is as your first step in delivering [Real Time Communications (WebRTC)](https://tokbox.com/about-webrtc) solutions on the OpenTok platform.
## Getting Help
We love to hear from you so if you have questions or comments, let us know! You can either:
- See for support options
- Tweet at us! We're [@VonageDev on Twitter](https://twitter.com/VonageDev)
- Or [join the Vonage Developer Community Slack](https://developer.nexmo.com/community/slack)
================================================
FILE: android/.gitignore
================================================
# built components files
*.jar
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# OSX files
.DS_Store
# Android Studio files
*.iml
.idea/
.idea/workspace.xml
.gradle
build/
*.ipr
*.iws
#libs
*.so
================================================
FILE: android/OneToOneSample/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
================================================
FILE: android/OneToOneSample/app/.gitignore
================================================
/build
/libs
================================================
FILE: android/OneToOneSample/app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "com.tokbox.android.onetosample"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.3"
archivesBaseName = "OneToOneSample-$versionName"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/LICENSE'
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:25.+'
compile 'com.android.support:design:25.+'
compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.13'
compile 'com.opentok.android:opentok-accelerator-core:1.0.+'
}
================================================
FILE: android/OneToOneSample/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/mserrano/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: android/OneToOneSample/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/java/com/tokbox/android/onetoonesample/MainActivity.java
================================================
package com.tokbox.android.onetoonesample;
import android.Manifest;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.opentok.android.OpentokError;
import com.tokbox.android.onetoonesample.config.OpenTokConfig;
import com.tokbox.android.onetoonesample.ui.PreviewCameraFragment;
import com.tokbox.android.onetoonesample.ui.PreviewControlFragment;
import com.tokbox.android.onetoonesample.ui.RemoteControlFragment;
import com.tokbox.android.otsdkwrapper.listeners.AdvancedListener;
import com.tokbox.android.otsdkwrapper.listeners.BasicListener;
import com.tokbox.android.otsdkwrapper.listeners.ListenerException;
import com.tokbox.android.otsdkwrapper.listeners.PausableAdvancedListener;
import com.tokbox.android.otsdkwrapper.listeners.PausableBasicListener;
import com.tokbox.android.otsdkwrapper.utils.MediaType;
import com.tokbox.android.otsdkwrapper.utils.OTConfig;
import com.tokbox.android.otsdkwrapper.utils.PreviewConfig;
import com.tokbox.android.otsdkwrapper.wrapper.OTWrapper;
import java.util.UUID;
import android.widget.FrameLayout;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements PreviewControlFragment.PreviewControlCallbacks,
RemoteControlFragment.RemoteControlCallbacks, PreviewCameraFragment.PreviewCameraCallbacks {
private final String LOG_TAG = MainActivity.class.getSimpleName();
private final String[] permissions = {Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA};
private final int permsRequestCode = 200;
private RelativeLayout mPreviewViewContainer;
private RelativeLayout mRemoteViewContainer;
private RelativeLayout.LayoutParams mLayoutParamsPreview;
private String mRemoteId;
private View mRemoteView;
private TextView mAlert;
//Audio only views
private RelativeLayout mLocalAudioOnlyView;
private RelativeLayout mRemoteAudioOnlyView;
private ImageView mLocalAudioOnlyImage;
//UI control bars fragments
private PreviewControlFragment mPreviewFragment;
private RemoteControlFragment mRemoteFragment;
private PreviewCameraFragment mCameraFragment;
private FragmentTransaction mFragmentTransaction;
//Loading dialog
ProgressDialog mProgressDialog;
//Permissions
private boolean mAudioPermission = false;
private boolean mVideoPermission = false;
//Communication status
private boolean isConnected = false;
private boolean isLocal = false;
private boolean isCallInProgress = false;
private OTWrapper mWrapper;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(LOG_TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPreviewViewContainer = (RelativeLayout) findViewById(R.id.publisherview);
mRemoteViewContainer = (RelativeLayout) findViewById(R.id.subscriberview);
mAlert = (TextView) findViewById(R.id.quality_warning);
//remote and local audio only view
mRemoteAudioOnlyView = (RelativeLayout) findViewById(R.id.remoteAudioOnlyView);
mLocalAudioOnlyView = (RelativeLayout) findViewById(R.id.localAudioOnlyView);
//request Marshmallow camera permission
if (ContextCompat.checkSelfPermission(this, permissions[1]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, permsRequestCode);
}
} else {
mVideoPermission = true;
mAudioPermission = true;
}
//init the wrapper
OTConfig config =
new OTConfig.OTConfigBuilder(OpenTokConfig.SESSION_ID, OpenTokConfig.TOKEN,
OpenTokConfig.API_KEY).name("one-to-one-sample-app").subscribeAutomatically(false).subscribeToSelf(false).build();
if ( config != null ) {
mWrapper = new OTWrapper(MainActivity.this, config);
mWrapper.addBasicListener(mBasicListener);
mWrapper.addAdvancedListener(mAdvancedListener);
if (mWrapper != null) {
mWrapper.connect();
}
//init controls fragments
if (savedInstanceState == null) {
mFragmentTransaction = getSupportFragmentManager().beginTransaction();
initCameraFragment(); //to swap camera
initPreviewFragment(); //to enable/disable local media
mFragmentTransaction.commitAllowingStateLoss();
}
//show connecting dialog
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setTitle("Please wait");
mProgressDialog.setMessage("Connecting...");
mProgressDialog.show();
}
else {
Log.e(LOG_TAG, "OpenTok credentials are invalid");
Toast.makeText(MainActivity.this, "Credentials are invalid", Toast.LENGTH_LONG).show();
this.finish();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
reloadViews();
}
@Override
protected void onPause() {
super.onPause();
if (mWrapper != null) {
mWrapper.pause();
}
}
@Override
protected void onResume() {
super.onResume();
if ( mWrapper != null ){
mWrapper.resume(true);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if ( mWrapper != null && isConnected ){
mWrapper.disconnect();
}
}
@Override
public void onRequestPermissionsResult(final int permsRequestCode, final String[] permissions,
int[] grantResults) {
switch (permsRequestCode) {
case 200:
mVideoPermission = grantResults[0] == PackageManager.PERMISSION_GRANTED;
mAudioPermission = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (!mVideoPermission || !mAudioPermission) {
final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(getResources().getString(R.string.permissions_denied_title));
builder.setMessage(getResources().getString(R.string.alert_permissions_denied));
builder.setPositiveButton("I'M SURE", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setNegativeButton("RE-TRY", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, permsRequestCode);
}
}
});
builder.show();
}
break;
}
}
public void showRemoteControlBar(View v) {
if ( mRemoteFragment != null && mRemoteId != null ) {
mRemoteFragment.show();
}
}
public boolean isCallInProgress() {
return isCallInProgress;
}
public OTWrapper getWrapper() {
return mWrapper;
}
//Private methods
//Init the local fragment
private void initPreviewFragment() {
mPreviewFragment = new PreviewControlFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.actionbar_preview_fragment_container, mPreviewFragment).commit();
}
//Inti the remote fragment
private void initRemoteFragment(String remoteId) {
mRemoteFragment = new RemoteControlFragment();
Bundle args = new Bundle();
args.putString("remoteId", remoteId);
mRemoteFragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.add(R.id.actionbar_remote_fragment_container, mRemoteFragment).commit();
}
//Init the local camera fragment
private void initCameraFragment() {
mCameraFragment = new PreviewCameraFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.camera_preview_fragment_container, mCameraFragment).commit();
}
//Clean views
private void cleanViewsAndControls() {
if ( mRemoteId != null ) {
mWrapper.removeRemote(mRemoteId);
mRemoteView = null;
setRemoteView(null);
}
if (isLocal) {
isLocal = false;
setLocalView(null);
}
if (mPreviewFragment != null)
mPreviewFragment.restart();
if (mRemoteFragment != null)
mRemoteFragment.restart();
}
//Reload views
private void reloadViews(){
mRemoteViewContainer.removeAllViews();
if (mRemoteId != null){
setRemoteView(mWrapper.getRemoteStreamStatus(mRemoteId).getView());
}
}
//Check if there are some connected remotes in the session
private void checkRemotes(){
if (mRemoteId != null){
//add the remote participant to the communication
mWrapper.addRemote(mRemoteId);
//check the status of the remote video stream
if (!mWrapper.isReceivedMediaEnabled(mRemoteId, MediaType.VIDEO)){
onRemoteAudioOnly(true);
}
else {
setRemoteView(mWrapper.getRemoteStreamStatus(mRemoteId).getView());
}
}
}
//Set the local participant view
private void setLocalView(View localView){
if (localView != null) {
mPreviewViewContainer.removeAllViews();
isLocal = true;
mLayoutParamsPreview = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
if (mRemoteId != null) {
mLayoutParamsPreview.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,
RelativeLayout.TRUE);
mLayoutParamsPreview.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,
RelativeLayout.TRUE);
mLayoutParamsPreview.width = (int) getResources().getDimension(R.dimen.preview_width);
mLayoutParamsPreview.height = (int) getResources().getDimension(R.dimen.preview_height);
mLayoutParamsPreview.rightMargin = (int) getResources().getDimension(R.dimen.preview_rightMargin);
mLayoutParamsPreview.bottomMargin = (int) getResources().getDimension(R.dimen.preview_bottomMargin);
}
mPreviewViewContainer.addView(localView, mLayoutParamsPreview);
}
else {
mPreviewViewContainer.removeAllViews();
}
}
//Set the remote participant view
private void setRemoteView(View remoteView) {
if (mPreviewViewContainer.getChildCount() > 0) {
setLocalView(mPreviewViewContainer.getChildAt(0)); //main preview view
}
if (remoteView != null) {
//show remote view
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
this.getResources().getDisplayMetrics().widthPixels, this.getResources()
.getDisplayMetrics().heightPixels);
mRemoteViewContainer.removeView(remoteView);
mRemoteViewContainer.addView(remoteView, layoutParams);
mRemoteViewContainer.setClickable(true);
if (mRemoteFragment != null)
mRemoteFragment.show();
} else { //view null --> remove view
if (mRemoteViewContainer.getChildCount() > 0) {
mRemoteViewContainer.removeAllViews();
}
mRemoteViewContainer.setClickable(false);
mRemoteAudioOnlyView.setVisibility(View.GONE);
}
}
//Set the remote audio only view
private void onRemoteAudioOnly(boolean enabled) {
if (mRemoteView != null) {
if (enabled) {
mRemoteView.setVisibility(View.GONE);
mRemoteAudioOnlyView.setVisibility(View.VISIBLE);
} else {
mRemoteAudioOnlyView.setVisibility(View.GONE);
mRemoteView.setVisibility(View.VISIBLE);
}
}
}
//Converts dp to real pixels, according to the screen density.
private int dpToPx(int dp) {
double screenDensity = this.getResources().getDisplayMetrics().density;
return (int) (screenDensity * (double) dp);
}
//Basic Listener from OTWrapper
private BasicListener mBasicListener =
new PausableBasicListener(new BasicListener() {
@Override
public void onConnected(OTWrapper otWrapper, int participantsCount, String connId, String data) throws ListenerException {
Log.i(LOG_TAG, "Connected to the session. Number of participants: "+participantsCount);
isConnected = true;
mProgressDialog.dismiss();
}
@Override
public void onDisconnected(OTWrapper otWrapper, int participantsCount, String connId, String data) throws ListenerException {
Log.i(LOG_TAG, "Connection dropped: "+connId);
if ( connId == mWrapper.getOwnConnId() ) {
Log.i(LOG_TAG, "Disconnected to the session");
cleanViewsAndControls();
}
}
@Override
public void onPreviewViewReady(OTWrapper otWrapper, View localView) throws ListenerException {
Log.i(LOG_TAG, "Local preview view is ready");
setLocalView(localView);
}
@Override
public void onPreviewViewDestroyed(OTWrapper otWrapper, View localView) throws ListenerException {
Log.i(LOG_TAG, "Local preview view is destroyed");
setLocalView(null);
}
@Override
public void onRemoteViewReady(OTWrapper otWrapper, View remoteView, String remoteId, String data) throws ListenerException {
Log.i(LOG_TAG, "Remove view is ready");
if ( remoteId == mRemoteId ) {
if (isCallInProgress()) {
setRemoteView(remoteView);
}
mRemoteView = remoteView;
}
}
@Override
public void onRemoteViewDestroyed(OTWrapper otWrapper, View remoteView, String remoteId) throws ListenerException {
Log.i(LOG_TAG, "Remote view is destroyed");
setRemoteView(null);
mRemoteView = null;
}
@Override
public void onStartedPublishingMedia(OTWrapper otWrapper, boolean screensharing) throws ListenerException {
Log.i(LOG_TAG, "Local started streaming video.");
//Check if there are some connected remotes
checkRemotes();
}
@Override
public void onStoppedPublishingMedia(OTWrapper otWrapper, boolean screensharing) throws ListenerException {
Log.i(LOG_TAG, "Local stopped streaming video.");
}
@Override
public void onRemoteJoined(OTWrapper otWrapper, String remoteId) throws ListenerException {
Log.i(LOG_TAG, "A new remote joined.");
if (mRemoteId == null){ //one-to-one, the first to arrive, will be the used
MainActivity.this.mRemoteId = remoteId;
initRemoteFragment(remoteId);
if (mWrapper.isPublishing()){
mWrapper.addRemote(mRemoteId);
}
}
}
@Override
public void onRemoteLeft(OTWrapper otWrapper, String remoteId) throws ListenerException {
Log.i(LOG_TAG, "A new remote left.");
if ( mRemoteId != null && remoteId == mRemoteId ) { //one-to-one
mRemoteId = null;
}
}
@Override
public void onRemoteVideoChanged(OTWrapper otWrapper, String remoteId, String reason, boolean videoActive, boolean subscribed) throws ListenerException {
Log.i(LOG_TAG, "Remote video changed");
if (isCallInProgress) {
if (reason.equals("quality")) {
//network quality alert
mAlert.setBackgroundResource(R.color.quality_alert);
mAlert.setTextColor(MainActivity.this.getResources().getColor(R.color.white));
mAlert.bringToFront();
mAlert.setVisibility(View.VISIBLE);
mAlert.postDelayed(new Runnable() {
public void run() {
mAlert.setVisibility(View.GONE);
}
}, 7000);
}
if (!videoActive) {
onRemoteAudioOnly(true); //video is not active
} else {
onRemoteAudioOnly(false);
}
}
}
@Override
public void onError(OTWrapper otWrapper, OpentokError error) throws ListenerException {
Log.i(LOG_TAG, "Error "+error.getErrorCode()+"-"+error.getMessage());
Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_LONG).show();
mWrapper.disconnect(); //end communication
mProgressDialog.dismiss();
cleanViewsAndControls(); //restart views
}
});
//Advanced Listener from OTWrapper
private AdvancedListener mAdvancedListener =
new PausableAdvancedListener(new AdvancedListener() {
@Override
public void onCameraChanged(OTWrapper otWrapper) throws ListenerException {
Log.i(LOG_TAG, "The camera changed");
}
@Override
public void onReconnecting(OTWrapper otWrapper) throws ListenerException {
Log.i(LOG_TAG, "The session is reconnecting.");
Toast.makeText(MainActivity.this, R.string.reconnecting, Toast.LENGTH_LONG).show();
}
@Override
public void onReconnected(OTWrapper otWrapper) throws ListenerException {
Log.i(LOG_TAG, "The session reconnected.");
Toast.makeText(MainActivity.this, R.string.reconnected, Toast.LENGTH_LONG).show();
}
@Override
public void onVideoQualityWarning(OTWrapper otWrapper, String remoteId) throws ListenerException {
Log.i(LOG_TAG, "The quality has degraded");
mAlert.setBackgroundResource(R.color.quality_warning);
mAlert.setTextColor(MainActivity.this.getResources().getColor(R.color.warning_text));
mAlert.bringToFront();
mAlert.setVisibility(View.VISIBLE);
mAlert.postDelayed(new Runnable() {
public void run() {
mAlert.setVisibility(View.GONE);
}
}, 7000);
}
@Override
public void onVideoQualityWarningLifted(OTWrapper otWrapper, String remoteId) throws ListenerException {
Log.i(LOG_TAG, "The quality has improved");
}
@Override
public void onError(OTWrapper otWrapper, OpentokError error) throws ListenerException {
Log.i(LOG_TAG, "Error " + error.getErrorCode() + "-" + error.getMessage());
Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_LONG).show();
mWrapper.disconnect(); //end communication
mProgressDialog.dismiss();
cleanViewsAndControls(); //restart views
}
});
//Audio local button event
@Override
public void onDisableLocalAudio(boolean audio) {
if (mWrapper != null) {
mWrapper.enableLocalMedia(MediaType.AUDIO, audio);
}
}
//Video local button event
@Override
public void onDisableLocalVideo(boolean video) {
if (mWrapper != null) {
mWrapper.enableLocalMedia(MediaType.VIDEO, video);
if (mRemoteId != null) {
if (!video) {
mLocalAudioOnlyImage = new ImageView(this);
mLocalAudioOnlyImage.setImageResource(R.drawable.avatar);
mLocalAudioOnlyImage.setBackgroundResource(R.drawable.bckg_audio_only);
mPreviewViewContainer.addView(mLocalAudioOnlyImage, mLayoutParamsPreview);
} else {
mPreviewViewContainer.removeView(mLocalAudioOnlyImage);
}
} else {
if (!video) {
mLocalAudioOnlyView.setVisibility(View.VISIBLE);
mPreviewViewContainer.addView(mLocalAudioOnlyView);
} else {
mLocalAudioOnlyView.setVisibility(View.GONE);
mPreviewViewContainer.removeView(mLocalAudioOnlyView);
}
}
}
}
//Remote control callbacks
@Override
public void onDisableRemoteAudio(boolean audio) {
if (mWrapper != null) {
mWrapper.enableReceivedMedia(mRemoteId, MediaType.AUDIO, audio);
}
}
@Override
public void onDisableRemoteVideo(boolean video) {
if (mWrapper != null) {
mWrapper.enableReceivedMedia(mRemoteId, MediaType.VIDEO, video);
}
}
//Camera control callback
@Override
public void onCameraSwap() {
if (mWrapper != null) {
mWrapper.cycleCamera();
}
}
@Override
public void onCall() {
if (mWrapper != null && isConnected) {
if (!isCallInProgress) {
mWrapper.startPublishingMedia(new PreviewConfig.PreviewConfigBuilder().
name("Tokboxer").build(), false);
if ( mPreviewFragment != null ) {
mPreviewFragment.setEnabled(true);
}
isCallInProgress = true;
} else {
mWrapper.stopPublishingMedia(false);
isCallInProgress = false;
cleanViewsAndControls();
}
}
}
}
================================================
FILE: android/OneToOneSample/app/src/main/java/com/tokbox/android/onetoonesample/config/OpenTokConfig.java
================================================
package com.tokbox.android.onetoonesample.config;
public class OpenTokConfig {
// *** Fill the following variables using your own Project info from the OpenTok dashboard ***
// *** https://dashboard.tokbox.com/projects ***
// Replace with a generated Session ID
public static final String SESSION_ID = "";
// Replace with a generated token (from the dashboard or using an OpenTok server SDK)
public static final String TOKEN = "";
// Replace with your OpenTok API key
public static final String API_KEY = "";
}
================================================
FILE: android/OneToOneSample/app/src/main/java/com/tokbox/android/onetoonesample/ui/PreviewCameraFragment.java
================================================
package com.tokbox.android.onetoonesample.ui;
import android.app.Activity;
import android.support.v4.app.Fragment;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import com.tokbox.android.onetoonesample.MainActivity;
import com.tokbox.android.onetoonesample.R;
public class PreviewCameraFragment extends Fragment {
private static final String LOGTAG = PreviewCameraFragment.class.getSimpleName();
private View mRootView;
private ImageButton mCameraBtn;
private PreviewCameraCallbacks mCameraCallbacks = cameraCallbacks;
public interface PreviewCameraCallbacks {
void onCameraSwap();
}
private static PreviewCameraCallbacks cameraCallbacks = new PreviewCameraCallbacks() {
@Override
public void onCameraSwap() {}
};
private View.OnClickListener mBtnClickListener = new View.OnClickListener() {
public void onClick(View v) {
cameraSwap();
}
};
@Override
public void onAttach(Context context) {
Log.i(LOGTAG, "OnAttach PreviewCameraFragment");
super.onAttach(context);
this.mCameraCallbacks = (PreviewCameraCallbacks) context;
}
@SuppressWarnings("deprecation")
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
this.mCameraCallbacks = (PreviewCameraCallbacks) activity;
}
}
@Override
public void onDetach() {
Log.i(LOGTAG, "OnDetach PreviewCameraFragment");
super.onDetach();
mCameraCallbacks = cameraCallbacks;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i(LOGTAG, "onCreate PreviewCameraFragment");
mRootView = inflater.inflate(R.layout.preview_camera_fragment, container, false);
mCameraBtn = (ImageButton) mRootView.findViewById(R.id.camera);
mCameraBtn.setOnClickListener(mBtnClickListener);
return mRootView;
}
public void cameraSwap() {
mCameraCallbacks.onCameraSwap();
}
}
================================================
FILE: android/OneToOneSample/app/src/main/java/com/tokbox/android/onetoonesample/ui/PreviewControlFragment.java
================================================
package com.tokbox.android.onetoonesample.ui;
import android.app.Activity;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import com.tokbox.android.onetoonesample.MainActivity;
import com.tokbox.android.onetoonesample.R;
import com.tokbox.android.otsdkwrapper.utils.MediaType;
public class PreviewControlFragment extends Fragment {
private static final String LOGTAG = MainActivity.class.getName();
private MainActivity mActivity;
private View rootView;
private ImageButton mAudioBtn;
private ImageButton mVideoBtn;
private ImageButton mCallBtn;
private VectorDrawableCompat drawableStartCall;
private VectorDrawableCompat drawableEndCall;
private VectorDrawableCompat drawableBckBtn;
private PreviewControlCallbacks mControlCallbacks = previewCallbacks;
public interface PreviewControlCallbacks {
void onDisableLocalAudio(boolean audio);
void onDisableLocalVideo(boolean video);
void onCall();
}
private static PreviewControlCallbacks previewCallbacks = new PreviewControlCallbacks() {
@Override
public void onDisableLocalAudio(boolean audio) { }
@Override
public void onDisableLocalVideo(boolean video) { }
@Override
public void onCall() { }
};
private View.OnClickListener mBtnClickListener = new View.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.localAudio:
updateLocalAudio();
break;
case R.id.localVideo:
updateLocalVideo();
break;
case R.id.call:
updateCall();
break;
}
}
};
@Override
public void onAttach(Context context) {
Log.i(LOGTAG, "OnAttach PreviewControlFragment");
super.onAttach(context);
this.mActivity = (MainActivity) context;
this.mControlCallbacks = (PreviewControlCallbacks) context;
}
@SuppressWarnings("deprecation")
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
this.mActivity = (MainActivity) activity;
this.mControlCallbacks = (PreviewControlCallbacks) activity;
}
}
@Override
public void onDetach() {
Log.i(LOGTAG, "onDetach PreviewControlFragment");
super.onDetach();
mControlCallbacks = previewCallbacks;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i(LOGTAG, "OnCreate PreviewControlFragment");
rootView = inflater.inflate(R.layout.preview_actionbar_fragment, container, false);
mAudioBtn = (ImageButton) rootView.findViewById(R.id.localAudio);
mVideoBtn = (ImageButton) rootView.findViewById(R.id.localVideo);
mCallBtn = (ImageButton) rootView.findViewById(R.id.call);
drawableStartCall = VectorDrawableCompat.create(getResources(), R.drawable.initiate_call_button, null);
drawableEndCall = VectorDrawableCompat.create(getResources(), R.drawable.end_call_button, null);
drawableBckBtn = VectorDrawableCompat.create(getResources(), R.drawable.bckg_icon, null);
mAudioBtn.setImageResource(mActivity.getWrapper().isLocalMediaEnabled(MediaType.AUDIO)
? R.drawable.mic_icon
: R.drawable.muted_mic_icon);
mAudioBtn.setBackground(drawableBckBtn);
mVideoBtn.setImageResource(mActivity.getWrapper().isLocalMediaEnabled(MediaType.VIDEO)
? R.drawable.video_icon
: R.drawable.no_video_icon);
mVideoBtn.setBackground(drawableBckBtn);
mCallBtn.setImageResource(mActivity.isCallInProgress()
? R.drawable.hang_up
: R.drawable.start_call);
mCallBtn.setBackground(mActivity.isCallInProgress()
? drawableEndCall
: drawableStartCall);
mCallBtn.setOnClickListener(mBtnClickListener);
setEnabled(mActivity.isCallInProgress());
return rootView;
}
public void updateLocalAudio() {
if (!mActivity.getWrapper().isLocalMediaEnabled(MediaType.AUDIO)) {
mControlCallbacks.onDisableLocalAudio(true);
mAudioBtn.setImageResource(R.drawable.mic_icon);
} else {
mControlCallbacks.onDisableLocalAudio(false);
mAudioBtn.setImageResource(R.drawable.muted_mic_icon);
}
}
public void updateLocalVideo() {
if (!mActivity.getWrapper().isLocalMediaEnabled(MediaType.VIDEO)){
mControlCallbacks.onDisableLocalVideo(true);
mVideoBtn.setImageResource(R.drawable.video_icon);
} else {
mControlCallbacks.onDisableLocalVideo(false);
mVideoBtn.setImageResource(R.drawable.no_video_icon);
}
}
public void updateCall() {
mCallBtn.setImageResource(!mActivity.isCallInProgress()
? R.drawable.hang_up
: R.drawable.start_call);
mCallBtn.setBackground(!mActivity.isCallInProgress()
? drawableEndCall
: drawableStartCall);
if ( mControlCallbacks != null )
mControlCallbacks.onCall();
}
public void setEnabled(boolean enabled) {
if (mVideoBtn != null && mAudioBtn != null) {
if (enabled) {
mAudioBtn.setOnClickListener(mBtnClickListener);
mVideoBtn.setOnClickListener(mBtnClickListener);
} else {
mAudioBtn.setOnClickListener(null);
mVideoBtn.setOnClickListener(null);
mAudioBtn.setImageResource(R.drawable.mic_icon);
mVideoBtn.setImageResource(R.drawable.video_icon);
}
}
}
public void restart() {
setEnabled(false);
mCallBtn.setBackground(drawableStartCall);
mCallBtn.setImageResource(R.drawable.start_call);
}
}
================================================
FILE: android/OneToOneSample/app/src/main/java/com/tokbox/android/onetoonesample/ui/RemoteControlFragment.java
================================================
package com.tokbox.android.onetoonesample.ui;
import android.app.Activity;
import android.support.v4.app.Fragment;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import com.tokbox.android.onetoonesample.MainActivity;
import com.tokbox.android.onetoonesample.R;
import com.tokbox.android.otsdkwrapper.utils.MediaType;
public class RemoteControlFragment extends Fragment {
private static final String LOGTAG = RemoteControlFragment.class.getSimpleName();
private static final int ANIMATION_DURATION = 7000;
private MainActivity mActivity;
private RelativeLayout mContainer;
private View mRootView;
private ImageButton mAudioBtn;
private ImageButton mVideoBtn;
private RemoteControlCallbacks mControlCallbacks = remoteCallbacks;
private String mRemoteId;
public interface RemoteControlCallbacks {
void onDisableRemoteAudio(boolean audio);
void onDisableRemoteVideo(boolean video);
}
private static RemoteControlCallbacks remoteCallbacks = new RemoteControlCallbacks() {
@Override
public void onDisableRemoteAudio(boolean audio) { }
@Override
public void onDisableRemoteVideo(boolean video) { }
};
private View.OnClickListener mBtnClickListener = new View.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.remoteAudio:
updateRemoteAudio();
break;
case R.id.remoteVideo:
updateRemoteVideo();
break;
}
}
};
@Override
public void onAttach(Context context) {
Log.i(LOGTAG, "OnAttach RemoteControlFragment");
super.onAttach(context);
this.mActivity = (MainActivity) context;
this.mControlCallbacks = (RemoteControlCallbacks) context;
}
@SuppressWarnings("deprecation")
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
this.mActivity = (MainActivity) activity;
this.mControlCallbacks = (RemoteControlCallbacks) activity;
}
if ( mRemoteId == null ) {
mRemoteId = getArguments().getString("remoteId");
}
}
@Override
public void onDetach() {
Log.i(LOGTAG, "OnDetach RemoteControlFragment");
super.onDetach();
mControlCallbacks = remoteCallbacks;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.i(LOGTAG, "OnCreate RemoteControlFragment");
mRootView = inflater.inflate(R.layout.remote_actionbar_fragment, container, false);
mContainer = (RelativeLayout) this.mActivity.findViewById(R.id.actionbar_remote_fragment_container);
mAudioBtn = (ImageButton) mRootView.findViewById(R.id.remoteAudio);
mVideoBtn = (ImageButton) mRootView.findViewById(R.id.remoteVideo);
mAudioBtn.setOnClickListener(mBtnClickListener);
mVideoBtn.setOnClickListener(mBtnClickListener);
return mRootView;
}
public void updateRemoteAudio(){
if(mRemoteId != null && !mActivity.getWrapper().isReceivedMediaEnabled(mRemoteId, MediaType.AUDIO)){
mControlCallbacks.onDisableRemoteAudio(true);
mAudioBtn.setImageResource(R.drawable.audio);
}
else {
mControlCallbacks.onDisableRemoteAudio(false);
mAudioBtn.setImageResource(R.drawable.no_audio);
}
}
public void updateRemoteVideo(){
if(mRemoteId != null && !mActivity.getWrapper().isReceivedMediaEnabled(mRemoteId, MediaType.VIDEO)){
mControlCallbacks.onDisableRemoteVideo(true);
mVideoBtn.setImageResource(R.drawable.video_icon);
}
else {
mControlCallbacks.onDisableRemoteVideo(false);
mVideoBtn.setImageResource(R.drawable.no_video_icon);
}
}
public void show(){
mContainer.setVisibility(View.VISIBLE);
mRootView.setVisibility(View.VISIBLE);
mContainer.postDelayed(new Runnable() {
public void run() {
mContainer.setVisibility(View.INVISIBLE);
}
}, ANIMATION_DURATION);
}
private void setEnabled(boolean enabled) {
if (mVideoBtn != null && mAudioBtn != null) {
if (!enabled) {
mAudioBtn.setImageResource(R.drawable.audio);
mVideoBtn.setImageResource(R.drawable.video_icon);
}
}
}
public void restart() {
setEnabled(false);
mContainer.setVisibility(View.INVISIBLE);
}
}
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/bckg_audio_only.xml
================================================
android:shape="rectangle">
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/bckg_icon.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/end_call_button.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/gradient_audionly.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/gradient_backg.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/initiate_call_button.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/drawable/preview.xml
================================================
android:shape="rectangle">
================================================
FILE: android/OneToOneSample/app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/layout/preview_actionbar_fragment.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/layout/preview_camera_fragment.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/layout/remote_actionbar_fragment.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/values/colors.xml
================================================
#5B5B5B
#393939
#343434
#6e6e6e
#7e7e7e
#ffffff
#000000
#6aadbf
#cb1e28
#cb1e28
#fef9c8
#ad7212
================================================
FILE: android/OneToOneSample/app/src/main/res/values/dimens.xml
================================================
1dp
71dp
71dp
52dp
52dp
30dp
14sp
90dp
78dp
24dp
75dp
================================================
FILE: android/OneToOneSample/app/src/main/res/values/strings.xml
================================================
OneToOneSample
Network connection is unstable.
The session is reconnecting
The session reconnected
Permissions Denied
Cannot use this app without requested permission. Please, grant audio and video permissions
Without these permissions the app is unable to make call.Are you sure you want to deny these permissions?
================================================
FILE: android/OneToOneSample/app/src/main/res/values/styles.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/values-v21/styles.xml
================================================
================================================
FILE: android/OneToOneSample/app/src/main/res/values-w820dp/dimens.xml
================================================
64dp
================================================
FILE: android/OneToOneSample/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
maven {
url "http://tokbox.bintray.com/maven"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: android/OneToOneSample/gradle/wrapper/gradle-wrapper.properties
================================================
#Thu Jan 19 16:41:06 CET 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
================================================
FILE: android/OneToOneSample/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: android/OneToOneSample/gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: android/OneToOneSample/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: android/OneToOneSample/settings.gradle
================================================
include ':app'
================================================
FILE: android/README.md
================================================

# OpenTok One-to-One Communication Sample App for Android
## Quick start
This section shows you how to prepare, build, and run the sample application.
### Install the project files
1. Clone the [OpenTok One-to-One Communication Sample App for Android repository](https://github.com/opentok/one-to-one-sample-apps/tree/master/android) from GitHub.
1. Start Android Studio.
1. In the **Quick Start** panel, click **Open an existing Android Studio Project**.
1. Navigate to the **android** folder, select the **OnetoOneSample** folder, and click **Choose**.
### Add the Accelerator Core Android
There are two options for installing the OpenTok SDK included in the Accelerator Pack Common for Android:
#### Using the repository
1. Clone the [OpenTok Accelerator Core repo](https://github.com/opentok/accelerator-core-android).
2. From your app project, right-click the app name and select **New > Module > Import Gradle Project**.
3. Navigate to the directory in which you cloned **OpenTok Accelerator Pack**, select **accelerator-core**, and click **Finish**.
4. Open the **build.gradle** file for the app and ensure the following lines have been added to the `dependencies` section:
```
compile project(':accelerator-core-android')
```
#### Using Maven
1. Modify the `build.gradle` for your solution and add the following code snippet to the section labeled `repositories`:
```gradle
maven { url "http://tokbox.bintray.com/maven" }
```
1. Modify the `build.gradle` for your activity and add the following code snippet to the section labeled `dependencies`:
```gradle
compile 'com.opentok.android:accelerator-core-android:+'
```
### Configure and build the app
Configure the sample app code. Then, build and run the app.
1. Get values for **API Key**, **Session ID**, and **Token**. See [OpenTok One-to-One Communication Sample App home page](../README.md) for important information.
In Android Studio, open **OpenTokConfig.java** and replace the following empty strings with the corresponding **API Key**, **Session ID**, and **Token** values:
```java
// Replace with a generated Session ID
public static final String SESSION_ID = "";
// Replace with a generated token
public static final String TOKEN = "";
// Replace with your OpenTok API key
public static final String API_KEY = "";
```
```java
//init the wrapper
OTConfig config =
new OTConfig.OTConfigBuilder(OpenTokConfig.SESSION_ID, OpenTokConfig.TOKEN,
OpenTokConfig.API_KEY).name("one-to-one-sample-app").subscribeAutomatically(true).subscribeToSelf(false).build();
if ( config != null ) {
mWrapper = new OTWrapper(MainActivity.this, config);
mWrapper.addBasicListener(mBasicListener);
mWrapper.addAdvancedListener(mAdvancedListener);
//...
}
```
## Exploring the code
This section describes best practices the sample app code uses to implement the one-to-one communication features.
For detail about the APIs used to develop this sample, see the [OpenTok Android SDK Reference](https://tokbox.com/developer/sdks/android/reference/) and [Android API Reference](http://developer.android.com/reference/packages.html).
### Class design
This section focuses on one-to-one communication features. For more information, see the [OpenTok One-to-One Communication Sample App](https://github.com/opentok/one-to-one-sample-apps).
| Class | Description |
| ------------- | ------------- |
| `MainActivity` | Implements the UI and media control callbacks. |
| `OpenTokConfig` | Stores the information required to configure the session and authorize the app to make requests to the backend server. |
| `PreviewControlFragment` | Manages the toolbar for the local audio and video controls, and the start/end call button. |
| `RemoteControlFragment` | Manages the icons to enable/disable the audio and video of the remote subscriber. |
| `PreviewCameraFragment ` | Manages the camera control. |
### Session and stream management
The `OTWrapper` class, included in the Accelerator Core for Android, is the backbone of the one-to-one communication features for the app.
This class uses the OpenTok API to initiate the client connection to the OpenTok session and manage the audio and video streams.
```java
mWrapper.connect();
mWrapper.startPublishingMedia(new PreviewConfig.PreviewConfigBuilder().
name("Tokboxer").build(), false);
mWrapper.enableLocalMedia(MediaType.AUDIO, audio);
mWrapper.disconnect();
```
The BasicListener and AdvancedListener interface monitor state changes in the communication, and defines the following methods:
```java
//Basic Listener from OTWrapper
private BasicListener mBasicListener =
new PausableBasicListener(new BasicListener() {
@Override
public void onConnected(OTWrapper otWrapper, int participantsCount, String connId, String data) throws ListenerException { //...}
@Override
public void onDisconnected(OTWrapper otWrapper, int participantsCount, String connId, String data) throws ListenerException { //...}
@Override
public void onPreviewViewReady(OTWrapper otWrapper, View localView) throws ListenerException { //...}
@Override
public void onRemoteViewReady(OTWrapper otWrapper, View remoteView, String remoteId, String data) throws ListenerException { //...}
@Override
public void onStartedPublishingMedia(OTWrapper otWrapper, boolean screensharing) throws ListenerException { //...}
//...
});
```
```java
//Advanced Listener from OTWrapper
private AdvancedListener mAdvancedListener =
new PausableAdvancedListener(new AdvancedListener() {
@Override
public void onCameraChanged(OTWrapper otWrapper) throws ListenerException { //... }
@Override
public void onReconnecting(OTWrapper otWrapper) throws ListenerException { //... }
@Override
public void onReconnected(OTWrapper otWrapper) throws ListenerException { //... }
@Override
public void onVideoQualityWarning(OTWrapper otWrapper, String remoteId) throws ListenerException { //... }
//...
});
```
### User interface
As described in [Class design](#class-design), the following classes set up and manage the UI fragments for the local and remote controls:
- `PreviewControlFragment`
- `RemoteControlFragment`
- `PreviewCameraFragment`
These classes work with the following `MainActivity` methods, which manage the views as the publisher and subscriber participate in the session.
## Requirements
To develop your one-to-one communication app:
1. Install [Android Studio](http://developer.android.com/intl/es/sdk/index.html)
1. Review the [OpenTok Android SDK Requirements](https://tokbox.com/developer/sdks/android/#developerandclientrequirements)
================================================
FILE: iOS/.gitignore
================================================
.DS_Store
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
xcuserdata
profile
*.moved-aside
DerivedData
*.xcdatamodeld
.idea/
# Pods - for those of you who use CocoaPods
Pods
*.framework
*.bundle
Podfile.lock
================================================
FILE: iOS/OneToOneSample.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
70F017EE1E004A7A008EF97D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F017E01E004A7A008EF97D /* AppDelegate.m */; };
70F017EF1E004A7A008EF97D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 70F017E11E004A7A008EF97D /* Assets.xcassets */; };
70F017F01E004A7A008EF97D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70F017E21E004A7A008EF97D /* LaunchScreen.storyboard */; };
70F017F11E004A7A008EF97D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70F017E41E004A7A008EF97D /* Main.storyboard */; };
70F017F31E004A7A008EF97D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F017E71E004A7A008EF97D /* main.m */; };
70F017F41E004A7A008EF97D /* MainView.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F017E91E004A7A008EF97D /* MainView.m */; };
70F017F51E004A7A008EF97D /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F017EB1E004A7A008EF97D /* MainViewController.m */; };
70F017F61E004A7A008EF97D /* UIView+Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 70F017ED1E004A7A008EF97D /* UIView+Helper.m */; };
A0A0E85D1E441C6C003F319C /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = A0A0E85C1E441C6C003F319C /* Podfile */; };
A0A0E85F1E441C70003F319C /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = A0A0E85E1E441C70003F319C /* README.md */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
70F017981E00458D008EF97D /* OneToOneSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OneToOneSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
70F017DF1E004A7A008EF97D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
70F017E01E004A7A008EF97D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
70F017E11E004A7A008EF97D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
70F017E31E004A7A008EF97D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
70F017E51E004A7A008EF97D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
70F017E61E004A7A008EF97D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
70F017E71E004A7A008EF97D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
70F017E81E004A7A008EF97D /* MainView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainView.h; sourceTree = ""; };
70F017E91E004A7A008EF97D /* MainView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainView.m; sourceTree = ""; };
70F017EA1E004A7A008EF97D /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; };
70F017EB1E004A7A008EF97D /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; };
70F017EC1E004A7A008EF97D /* UIView+Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Helper.h"; sourceTree = ""; };
70F017ED1E004A7A008EF97D /* UIView+Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Helper.m"; sourceTree = ""; };
A0A0E85C1E441C6C003F319C /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; };
A0A0E85E1E441C70003F319C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
70F017951E00458D008EF97D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
70F0178F1E00458D008EF97D = {
isa = PBXGroup;
children = (
A0A0E85E1E441C70003F319C /* README.md */,
A0A0E85C1E441C6C003F319C /* Podfile */,
70F017DE1E004A7A008EF97D /* SampleApp */,
70F017991E00458D008EF97D /* Products */,
);
sourceTree = "";
};
70F017991E00458D008EF97D /* Products */ = {
isa = PBXGroup;
children = (
70F017981E00458D008EF97D /* OneToOneSample.app */,
);
name = Products;
sourceTree = "";
};
70F017DE1E004A7A008EF97D /* SampleApp */ = {
isa = PBXGroup;
children = (
70F017DF1E004A7A008EF97D /* AppDelegate.h */,
70F017E01E004A7A008EF97D /* AppDelegate.m */,
70F017F71E004A88008EF97D /* Resources */,
70F017E71E004A7A008EF97D /* main.m */,
70F017E81E004A7A008EF97D /* MainView.h */,
70F017E91E004A7A008EF97D /* MainView.m */,
70F017EA1E004A7A008EF97D /* MainViewController.h */,
70F017EB1E004A7A008EF97D /* MainViewController.m */,
70F017EC1E004A7A008EF97D /* UIView+Helper.h */,
70F017ED1E004A7A008EF97D /* UIView+Helper.m */,
);
path = SampleApp;
sourceTree = "";
};
70F017F71E004A88008EF97D /* Resources */ = {
isa = PBXGroup;
children = (
70F017E11E004A7A008EF97D /* Assets.xcassets */,
70F017E21E004A7A008EF97D /* LaunchScreen.storyboard */,
70F017E41E004A7A008EF97D /* Main.storyboard */,
70F017E61E004A7A008EF97D /* Info.plist */,
);
name = Resources;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
70F017971E00458D008EF97D /* OneToOneSample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 70F017BA1E00458D008EF97D /* Build configuration list for PBXNativeTarget "OneToOneSample" */;
buildPhases = (
70F017941E00458D008EF97D /* Sources */,
70F017951E00458D008EF97D /* Frameworks */,
70F017961E00458D008EF97D /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = OneToOneSample;
productName = OneToOneSample;
productReference = 70F017981E00458D008EF97D /* OneToOneSample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
70F017901E00458D008EF97D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0810;
ORGANIZATIONNAME = "Tokbox, Inc.";
TargetAttributes = {
70F017971E00458D008EF97D = {
CreatedOnToolsVersion = 8.1;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 70F017931E00458D008EF97D /* Build configuration list for PBXProject "OneToOneSample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 70F0178F1E00458D008EF97D;
productRefGroup = 70F017991E00458D008EF97D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
70F017971E00458D008EF97D /* OneToOneSample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
70F017961E00458D008EF97D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
70F017F11E004A7A008EF97D /* Main.storyboard in Resources */,
70F017F01E004A7A008EF97D /* LaunchScreen.storyboard in Resources */,
A0A0E85D1E441C6C003F319C /* Podfile in Resources */,
70F017EF1E004A7A008EF97D /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
70F017941E00458D008EF97D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A0A0E85F1E441C70003F319C /* README.md in Sources */,
70F017EE1E004A7A008EF97D /* AppDelegate.m in Sources */,
70F017F61E004A7A008EF97D /* UIView+Helper.m in Sources */,
70F017F51E004A7A008EF97D /* MainViewController.m in Sources */,
70F017F41E004A7A008EF97D /* MainView.m in Sources */,
70F017F31E004A7A008EF97D /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
70F017E21E004A7A008EF97D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
70F017E31E004A7A008EF97D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
70F017E41E004A7A008EF97D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
70F017E51E004A7A008EF97D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
70F017B81E00458D008EF97D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
70F017B91E00458D008EF97D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
70F017BB1E00458D008EF97D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = SampleApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tokbox.OneToOneSample;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
70F017BC1E00458D008EF97D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = SampleApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.tokbox.OneToOneSample;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
70F017931E00458D008EF97D /* Build configuration list for PBXProject "OneToOneSample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
70F017B81E00458D008EF97D /* Debug */,
70F017B91E00458D008EF97D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
70F017BA1E00458D008EF97D /* Build configuration list for PBXNativeTarget "OneToOneSample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
70F017BB1E00458D008EF97D /* Debug */,
70F017BC1E00458D008EF97D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 70F017901E00458D008EF97D /* Project object */;
}
================================================
FILE: iOS/OneToOneSample.xcodeproj/xcshareddata/xcschemes/OneToOneSample.xcscheme
================================================
================================================
FILE: iOS/Podfile
================================================
platform :ios, '9.0'
target 'OneToOneSample' do
pod 'OTAcceleratorCore', '1.0.3'
pod 'SVProgressHUD'
end
================================================
FILE: iOS/README.md
================================================

# OpenTok One-to-One Communication Sample App for iOS
Version 1.3
## Quick start
This section shows you how to prepare, build, and run the sample application. The app is built by the [Accelerator Core iOS](https://github.com/opentok/accelerator-core-ios).
### Install the project files
Use CocoaPods to install the project files and dependencies.
1. Install CocoaPods as described in [CocoaPods Getting Started](https://guides.cocoapods.org/using/getting-started.html#getting-started).
1. In Terminal, `cd` to your project directory and type `pod install`.
1. Reopen your project in Xcode using the new `*.xcworkspace` file.
### Configure and build the app
Configure the sample app code. Then, build and run the app.
1. Get values for **API Key**, **Session ID**, and **Token**. See [OpenTok One-to-One Communication Sample App home page](../README.md) for important information.
1. Replace the following empty strings with the corresponding **API Key**, **Session ID**, and **Token** values:
```objc
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[OTAcceleratorSession setOpenTokApiKey:@""
sessionId:@""
token:@""];
return YES;
}
```
1. Use Xcode to build and run the app on an iOS simulator or device.
## Exploring the code
For detail about the APIs used to develop this sample, see the [OpenTok iOS SDK Reference](https://tokbox.com/developer/sdks/ios/reference/).
_**NOTE:** This sample app collects anonymous usage data for internal TokBox purposes only. Please do not modify or remove any logging code from this sample application._
### Session and stream management
The `OTOneToOneCommunicator` class is the backbone of the one-to-one communication features for the app. This class conforms to the protocols that initiate the client connection to the OpenTok session and sets up the listeners for the publisher and subscriber streams:
```objc
[self.oneToOneCommunicator connectWithHandler:^(OTOneToOneCommunicationSignal signal, NSError *error) {
if (!error) {
if (signal == OTSessionDidConnect) {
// publisher view is available, now you can add subscriber view to your desired view
}
else if (signal == OTSubscriberDidConnect) {
// subscriber view is available, now you can add subscriber view to your desired view
}
}
else {
}
}];
```
The following enum notifies the main controller of all session, publisher, and subscriber events:
```objc
typedef NS_ENUM(NSUInteger, OTOneToOneCommunicationSignal) {
OTSessionDidConnect = 0,
OTSessionDidDisconnect,
OTSessionDidFail,
OTSessionStreamCreated,
OTSessionStreamDestroyed,
OTSessionDidBeginReconnecting,
OTSessionDidReconnect,
OTPublisherDidFail,
OTPublisherStreamCreated,
OTPublisherStreamDestroyed,
OTSubscriberDidConnect,
OTSubscriberDidFail,
OTSubscriberVideoDisabledByPublisher,
OTSubscriberVideoDisabledBySubscriber,
OTSubscriberVideoDisabledByBadQuality,
OTSubscriberVideoEnabledByPublisher,
OTSubscriberVideoEnabledBySubscriber,
OTSubscriberVideoEnabledByGoodQuality,
OTSubscriberVideoDisableWarning,
OTSubscriberVideoDisableWarningLifted,
};
```
## Requirements
To develop your one-to-one communication app:
1. Install Xcode version 5 or later.
2. Review the [OpenTok iOS SDK Requirements](https://tokbox.com/developer/sdks/ios/).
================================================
FILE: iOS/SampleApp/AppDelegate.h
================================================
//
// AppDelegate.h
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import
@class OTAcceleratorSession;
@interface AppDelegate : UIResponder
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong, readonly) OTAcceleratorSession* acceleratorSession;
@end
================================================
FILE: iOS/SampleApp/AppDelegate.m
================================================
//
// AppDelegate.m
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import "AppDelegate.h"
#import "OTAcceleratorSession.h"
@interface AppDelegate ()
@property (nonatomic, strong) OTAcceleratorSession* acceleratorSession;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.acceleratorSession = [[OTAcceleratorSession alloc] initWithOpenTokApiKey:@"<# Replace #>"
sessionId:@"<# Replace #>"
token:@"<# Replace #>"];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
}
@end
================================================
FILE: iOS/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-40@2x-1.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-40@3x-1.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-Small.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-40@2x-2.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-40.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-40@2x-3.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-83.5@2x.png",
"scale" : "2x"
},
{
"size" : "24x24",
"idiom" : "watch",
"scale" : "2x",
"role" : "notificationCenter",
"subtype" : "38mm"
},
{
"size" : "27.5x27.5",
"idiom" : "watch",
"scale" : "2x",
"role" : "notificationCenter",
"subtype" : "42mm"
},
{
"size" : "29x29",
"idiom" : "watch",
"filename" : "Icon-Small@2x.png",
"role" : "companionSettings",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "watch",
"filename" : "Icon-Small@3x.png",
"role" : "companionSettings",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "watch",
"scale" : "2x",
"role" : "appLauncher",
"subtype" : "38mm"
},
{
"size" : "86x86",
"idiom" : "watch",
"scale" : "2x",
"role" : "quickLook",
"subtype" : "38mm"
},
{
"size" : "98x98",
"idiom" : "watch",
"scale" : "2x",
"role" : "quickLook",
"subtype" : "42mm"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/audio.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "audio.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "audio@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "audio@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/hangUp.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "hangUp.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "hangUp@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "hangUp@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/mic.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "mic.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "mic@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "mic@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/mutedMic.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "mutedMicLineCopy.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "mutedMicLineCopy@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "mutedMicLineCopy@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/noAudio.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "noSoundCopy.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "noSoundCopy@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "noSoundCopy@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/noVideo.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "noVideoIcon.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "noVideoIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "noVideoIcon@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/reverse cameras.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "reverse cameras.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "reverse cameras@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "reverse cameras@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/startCall.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "startCall.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "startCall@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "startCall@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Assets.xcassets/video.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "videoIcon.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "videoIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "videoIcon@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: iOS/SampleApp/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: iOS/SampleApp/Base.lproj/Main.storyboard
================================================
================================================
FILE: iOS/SampleApp/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1
LSRequiresIPhoneOS
NSCameraUsageDescription
$(PRODUCT_NAME) uses camera
NSMicrophoneUsageDescription
$(PRODUCT_NAME) uses microphone
UIBackgroundModes
audio
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UIRequiredDeviceCapabilities
armv7
UIStatusBarHidden
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
================================================
FILE: iOS/SampleApp/MainView.h
================================================
//
// MainView.h
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import
@interface MainView : UIView
// publisher view
- (void)addPublisherView:(UIView *)publisherView;
- (void)removePublisherView;
- (void)connectCallHolder:(BOOL)connected;
- (void)updatePublisherAudio:(BOOL)connected;
- (void)updatePublisherVideo:(BOOL)connected;
// subscriber view
- (void)addSubscribeView:(UIView *)subscriberView;
- (void)removeSubscriberView;
- (void)updateSubscriberAudioButton:(BOOL)connected;
- (void)updateSubscriberVideoButton:(BOOL)connected;
- (void)showSubscriberControls:(BOOL)shown;
// other controls
- (void)enableControlButtonsForCall:(BOOL)enabled;
- (void)showReverseCameraButton;
- (void)resetAllControl;
@end
================================================
FILE: iOS/SampleApp/MainView.m
================================================
//
// MainView.m
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import "MainView.h"
#import "UIView+Helper.h"
@interface MainView()
@property (weak, nonatomic) IBOutlet UIView *publisherView;
@property (weak, nonatomic) IBOutlet UIView *subscriberView;
// 3 action buttons at the bottom of the view
@property (weak, nonatomic) IBOutlet UIButton *publisherVideoButton;
@property (weak, nonatomic) IBOutlet UIButton *callButton;
@property (weak, nonatomic) IBOutlet UIButton *publisherAudioButton;
@property (weak, nonatomic) IBOutlet UIButton *reverseCameraButton;
@property (weak, nonatomic) IBOutlet UIButton *subscriberVideoButton;
@property (weak, nonatomic) IBOutlet UIButton *subscriberAudioButton;
@end
@implementation MainView
- (void)awakeFromNib {
[super awakeFromNib];
self.publisherView.hidden = YES;
self.publisherView.alpha = 1;
self.publisherView.layer.borderWidth = 1;
self.publisherView.layer.borderColor = [UIColor whiteColor].CGColor;
self.publisherView.layer.backgroundColor = [UIColor grayColor].CGColor;
self.publisherView.layer.cornerRadius = 3;
[self drawBorderOn:self.publisherAudioButton withWhiteBorder:YES];
[self drawBorderOn:self.callButton withWhiteBorder:NO];
[self drawBorderOn:self.publisherVideoButton withWhiteBorder:YES];
[self showSubscriberControls:NO];
}
- (void)drawBorderOn:(UIView *)view
withWhiteBorder:(BOOL)withWhiteBorder {
view.layer.cornerRadius = (view.bounds.size.width / 2);
if (withWhiteBorder) {
view.layer.borderWidth = 1;
view.layer.borderColor = [UIColor whiteColor].CGColor;
}
}
#pragma mark - publisher view
- (void)addPublisherView:(UIView *)publisherView {
[self.publisherView setHidden:NO];
[self.publisherView addSubview:publisherView];
publisherView.translatesAutoresizingMaskIntoConstraints = NO;
[publisherView addAttachedLayoutConstantsToSuperview];
}
- (void)removePublisherView {
[self.publisherView setHidden:YES];
[self.publisherView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
- (void)connectCallHolder:(BOOL)connected {
[self.callButton setImage:connected ? [UIImage imageNamed:@"hangUp"] : [UIImage imageNamed:@"startCall"] forState:UIControlStateNormal];
self.callButton.layer.backgroundColor = connected ? [UIColor colorWithRed:(205/255.0) green:(32/255.0) blue:(40/255.0) alpha:1.0].CGColor : [UIColor colorWithRed:(106/255.0) green:(173/255.0) blue:(191/255.0) alpha:1.0].CGColor;
}
- (void)updatePublisherAudio:(BOOL)connected {
[self.publisherAudioButton setImage:connected ? [UIImage imageNamed:@"mic"] : [UIImage imageNamed:@"mutedMic"] forState:UIControlStateNormal];
}
- (void)updatePublisherVideo:(BOOL)connected {
[self.publisherVideoButton setImage:connected ? [UIImage imageNamed:@"video"] : [UIImage imageNamed:@"noVideo"] forState:UIControlStateNormal];
}
#pragma mark - subscriber view
- (void)addSubscribeView:(UIView *)subscriberView {
[self.subscriberView addSubview:subscriberView];
subscriberView.translatesAutoresizingMaskIntoConstraints = NO;
[subscriberView addAttachedLayoutConstantsToSuperview];
}
- (void)removeSubscriberView {
[self.subscriberView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
- (void)updateSubscriberAudioButton:(BOOL)connected {
[self.subscriberAudioButton setImage:connected ? [UIImage imageNamed:@"audio"] : [UIImage imageNamed:@"noAudio"] forState:UIControlStateNormal];
}
- (void)updateSubscriberVideoButton:(BOOL)connected {
[self.subscriberVideoButton setImage:connected ? [UIImage imageNamed:@"video"] : [UIImage imageNamed:@"noVideo"] forState:UIControlStateNormal];
}
- (void)showSubscriberControls:(BOOL)shown {
[self.subscriberAudioButton setHidden:!shown];
[self.subscriberVideoButton setHidden:!shown];
}
#pragma mark - other controls
- (void)enableControlButtonsForCall:(BOOL)enabled {
[self.subscriberAudioButton setEnabled:enabled];
[self.subscriberVideoButton setEnabled:enabled];
[self.publisherVideoButton setEnabled:enabled];
[self.publisherAudioButton setEnabled:enabled];
}
- (void)showReverseCameraButton; {
self.reverseCameraButton.hidden = NO;
}
- (void)resetAllControl {
[self removePublisherView];
[self connectCallHolder:NO];
[self updatePublisherAudio:YES];
[self updatePublisherVideo:YES];
[self updateSubscriberAudioButton:YES];
[self updateSubscriberVideoButton:YES];
[self enableControlButtonsForCall:NO];
}
@end
================================================
FILE: iOS/SampleApp/MainViewController.h
================================================
//
// MainViewController.h
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import
@interface MainViewController : UIViewController
@end
================================================
FILE: iOS/SampleApp/MainViewController.m
================================================
//
// MainViewController.m
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import "MainView.h"
#import "MainViewController.h"
#import "OTOneToOneCommunicator.h"
#import "AppDelegate.h"
#import
#define MAKE_WEAK(self) __weak typeof(self) weak##self = self
#define MAKE_STRONG(self) __strong typeof(weak##self) strong##self = weak##self
@interface MainViewController ()
@property (nonatomic) MainView *mainView;
@property (nonatomic) OTOneToOneCommunicator *oneToOneCommunicator;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mainView = (MainView *)self.view;
self.oneToOneCommunicator = [[OTOneToOneCommunicator alloc] init];
self.oneToOneCommunicator.dataSource = self;
#if !(TARGET_OS_SIMULATOR)
[self.mainView showReverseCameraButton];
#endif
}
- (IBAction)publisherCallButtonPressed:(UIButton *)sender {
if (!self.oneToOneCommunicator.isCallEnabled) {
[SVProgressHUD show];
MAKE_WEAK(self);
[self.oneToOneCommunicator connectWithHandler:^(OTCommunicationSignal signal, NSError *error) {
MAKE_STRONG(self);
strongself.oneToOneCommunicator.publisherView.showAudioVideoControl = NO;
if (!error) {
[strongself handleCommunicationSignal:signal];
}
else {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}
}];
}
else {
[SVProgressHUD popActivity];
[self.oneToOneCommunicator disconnect];
[self.mainView resetAllControl];
}
}
- (void)handleCommunicationSignal:(OTCommunicationSignal)signal {
switch (signal) {
case OTPublisherCreated: {
[SVProgressHUD popActivity];
[self.mainView connectCallHolder:self.oneToOneCommunicator.isCallEnabled];
[self.mainView enableControlButtonsForCall:YES];
[self.mainView addPublisherView:self.oneToOneCommunicator.publisherView];
break;
}
case OTPublisherDestroyed: {
[self.mainView removePublisherView];
NSLog(@"Your publishing feed stops streaming in OpenTok");
break;
}
case OTSubscriberCreated: {
[SVProgressHUD show];
}
case OTSubscriberReady: {
[SVProgressHUD popActivity];
[self.mainView addSubscribeView:self.oneToOneCommunicator.subscriberView];
break;
}
case OTSubscriberDestroyed:{
[self.mainView removeSubscriberView];
break;
}
case OTSessionDidBeginReconnecting: {
[SVProgressHUD showInfoWithStatus:@"Reconnecting"];
break;
}
case OTSessionDidReconnect: {
[SVProgressHUD popActivity];
break;
}
case OTSubscriberVideoDisabledByBadQuality:
case OTSubscriberVideoDisabledBySubscriber: {
NSLog(@"The remote has disabled the video");
break;
}
case OTSubscriberVideoDisabledByPublisher:{
self.oneToOneCommunicator.subscribeToVideo = NO;
break;
}
case OTSubscriberVideoEnabledByGoodQuality:
case OTSubscriberVideoEnabledBySubscriber:{
NSLog(@"The remote has enabled the video");
break;
}
case OTSubscriberVideoEnabledByPublisher:{
self.oneToOneCommunicator.subscribeToVideo = YES;
break;
}
case OTSubscriberVideoDisableWarning:{
self.oneToOneCommunicator.subscribeToVideo = NO;
[SVProgressHUD showErrorWithStatus:@"Network connection is unstable."];
break;
}
case OTSubscriberVideoDisableWarningLifted:{
self.oneToOneCommunicator.subscribeToVideo = YES;
break;
}
default: break;
}
}
- (IBAction)publisherAudioButtonPressed:(UIButton *)sender {
self.oneToOneCommunicator.publishAudio = !self.oneToOneCommunicator.publishAudio;
[self.mainView updatePublisherAudio:self.oneToOneCommunicator.publishAudio];
}
- (IBAction)publisherVideoButtonPressed:(UIButton *)sender {
self.oneToOneCommunicator.publishVideo = !self.oneToOneCommunicator.publishVideo;
[self.mainView updatePublisherVideo:self.oneToOneCommunicator.publishVideo];
}
- (IBAction)publisherCameraButtonPressed:(UIButton *)sender {
self.oneToOneCommunicator.cameraPosition = self.oneToOneCommunicator.cameraPosition == AVCaptureDevicePositionBack ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack
}
- (IBAction)subscriberVideoButtonPressed:(UIButton *)sender {
self.oneToOneCommunicator.subscribeToVideo = !self.oneToOneCommunicator.subscribeToVideo;
[self.mainView updateSubscriberVideoButton:self.oneToOneCommunicator.subscribeToVideo];
}
- (IBAction)subscriberAudioButtonPressed:(UIButton *)sender {
self.oneToOneCommunicator.subscribeToAudio = !self.oneToOneCommunicator.subscribeToAudio;
[self.mainView updateSubscriberAudioButton:self.oneToOneCommunicator.subscribeToAudio];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if (self.oneToOneCommunicator.subscriberView){
[self.mainView showSubscriberControls:YES];
}
[self.mainView performSelector:@selector(showSubscriberControls:)
withObject:@(NO)
afterDelay:7.0];
}
- (OTAcceleratorSession *)sessionOfOTOneToOneCommunicator:(OTOneToOneCommunicator *)oneToOneCommunicator {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
return appDelegate.acceleratorSession;
}
@end
================================================
FILE: iOS/SampleApp/UIView+Helper.h
================================================
//
// UIView+Helper.h
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import
@interface UIView (Helper)
- (void)addAttachedLayoutConstantsToSuperview;
@end
================================================
FILE: iOS/SampleApp/UIView+Helper.m
================================================
//
// UIView+Helper.m
//
// Copyright © 2016 Tokbox, Inc. All rights reserved.
//
#import "UIView+Helper.h"
@implementation UIView (Helper)
- (void)addAttachedLayoutConstantsToSuperview {
if (!self.superview) {
return;
}
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
[NSLayoutConstraint activateConstraints:@[top, leading, trailing, bottom]];
}
@end
================================================
FILE: iOS/SampleApp/main.m
================================================
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
================================================
FILE: js/.eslintrc.json
================================================
{
"extends": "airbnb",
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 5
},
"plugins": [
"react"
],
"rules": {
"no-underscore-dangle": 0,
"vars-on-top": 0,
"padded-blocks": 0,
"no-var": 0,
"comma-dangle": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"object-shorthand": 0,
"no-unused-expressions": [2, {
"allowTernary": true,
"allowShortCircuit": true
}],
"global-require": 0
},
"globals": {
"$": true,
"_": true
}
}
================================================
FILE: js/.gitignore
================================================
node_modules
================================================
FILE: js/.jsbeautifyrc
================================================
{
"js":{
"indent_size": 2,
"space_after_anon_function": true,
"end_with_newline": true,
"brace_style": "collaps-preserve-inline"
}
}
================================================
FILE: js/Procfile
================================================
web: node server.js
================================================
FILE: js/README.md
================================================

# OpenTok One-to-One Communication Sample App for JavaScript
Version 1.3
## Quick start
This section shows you how to prepare and run the sample application. The app is built by the [Accelerator Core JS](https://github.com/opentok/accelerator-core-js).
### Configuring the app
Configure the sample app code. Then, build and run the app.
1. Get values for **API Key**, **Session ID**, and **Token**. See [OpenTok One-to-One Communication Sample App home page](../README.md) for important information.
2. In **app.js**, replace the following empty strings with the corresponding **API Key**, **Session ID**, and **Token** values:
```javascript
apiKey: '', // Replace with your OpenTok API Key
sessionId: '', // Replace with a generated Session ID
token: '', // Replace with a generated token (from the dashboard or using an OpenTok server SDK)
```
### Deploying and running the app
```javascript
$ npm install
$ npm run build
$ node server.js
```
The web page that loads the sample app for JavaScript must be served over HTTP/HTTPS. Browser security limitations prevent you from publishing video using a `file://` path, as discussed in the OpenTok.js [Release Notes](https://www.tokbox.com/developer/sdks/js/release-notes.html#knownIssues). To support clients running [Chrome 47 or later](https://groups.google.com/forum/#!topic/discuss-webrtc/sq5CVmY69sc), HTTPS is required. A [Node](https://nodejs.org/en/) server will work, as will [MAMP](https://www.mamp.info/) or [XAMPP](https://www.apachefriends.org/index.html). You can also use a cloud service such as [Heroku](https://www.heroku.com/) to host the application.
## Exploring the code
For details about how to use the Accelerator Core in the sample app, see [here](https://github.com/opentok/accelerator-core-js#sample-applications).
================================================
FILE: js/package.json
================================================
{
"name": "one-to-one-sample",
"version": "1.0.4",
"description": "One to One sample app",
"main": "server.js",
"repository": {
"type": "git",
"url": "https://github.com/opentok/one-to-one-sample-apps"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cp ./node_modules/opentok-accelerator-core/browser/opentok-acc-core.js ./public/js/components/opentok-acc-core.js",
"start": "node server.js"
},
"author": "adrice727@gmail.com",
"license": "MIT",
"dependencies": {
"express": "^4.14.1",
"body-parser": "^1.16.0",
"opentok-accelerator-core": "*"
}
}
================================================
FILE: js/public/css/style.css
================================================
html,
body {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
.clickable {
cursor: pointer;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.App-header {
background-color: #222;
height: 40px;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.App-header h1 {
font-size: 16px;
font-weight: 200;
}
.App-logo {
height: 60%;
width: auto;
}
.App-main {
position: relative;
width: 75vw;
height: calc(75vw * .6);
margin: 10px auto;
border: 1px solid lightblue;
}
.App-control-container {
position: absolute;
height: 100%;
width: 60px;
left: -60px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.App-control-container.hidden {
display: none;
}
.App-control-container .ots-video-control {
width: 50px;
height: 50px;
margin: 20px 0 !important;
border: 2px solid white;
border-radius: 50%;
background-position: center;
background-color: rgba(27, 134, 144, 0.4);
background-color: lightgrey;
background-repeat: no-repeat;
cursor: pointer;
}
.App-control-container .ots-video-control.audio {
background-image: url(https://assets.tokbox.com/solutions/images/icon-mic.png);
}
.App-control-container .ots-video-control.audio:hover, .App-control-container .ots-video-control.audio.muted {
background-image: url(https://assets.tokbox.com/solutions/images/icon-muted-mic.png);
}
.App-control-container .ots-video-control.video {
background-image: url(https://assets.tokbox.com/solutions/images/icon-video.png);
}
.App-control-container .ots-video-control.video.muted {
background-image: url(https://assets.tokbox.com/solutions/images/icon-no-video.png);
}
.App-control-container .ots-video-control.end-call {
background-image: url(https://assets.tokbox.com/solutions/images/icon-hang-up.png);
background-color: red;
}
.App-video-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.App-mask {
width: 100%;
height: 100%;
position: relative;
color: white;
background: rgba(27, 134, 144, 0.4);
display: flex;
justify-content: center;
align-items: center;
}
.App-mask.hidden {
display: none;
}
.App-mask .react-spinner {
position: absolute;
}
.App-mask .message {
font-weight: 200;
}
.App-mask .message.with-spinner {
position: absolute;
top: 57.5%;
}
.App-mask .message.button {
border: 1px solid white;
padding: 20px 40px;
border-radius: 6px;
}
.App-video-container .video-container {
width: 100%;
height: 100%;
display: flex;
}
.App-video-container .video-container.small {
position: absolute;
top: 20px;
right: 20px;
width: 160px;
height: 96px;
border: 1px solid #fcba00;
z-index: 2;
}
.App-video-container .video-container.small.left {
left: 20px;
border: 1px solid #00fcc2;
}
.App-video-container .video-container.hidden {
display: none;
}
.App-video-container .video-container.active-2 .OT_subscriber {
width: 50%;
}
.App-video-container .video-container.active-3 .OT_subscriber {
width: calc(100%/3) !important;
}
.App-video-container .video-container.active-4 {
flex-wrap: wrap;
}
.App-video-container .video-container.active-4 .OT_subscriber {
width: 50% !important;
height: 50% !important;
}
progress-spinner {
display: inline-block;
width: 1em;
height: 1em;
border: 1px solid transparent;
border-top-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
-webkit-animation: rotate 800ms linear infinite;
animation: rotate 800ms linear infinite;
}
progress-spinner[dark] {
border-top-color: rgba(255, 255, 255, 0.6);
}
progress-spinner[dotted] {
border-width: 0;
border-style: dotted;
border-top-width: 2px;
}
@-webkit-keyframes rotate {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes rotate {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
================================================
FILE: js/public/index.html
================================================
OT Accelerator Core
================================================
FILE: js/public/js/app.js
================================================
/* global otCore */
const options = {
credentials: {
apiKey: "", //Replace with your OpenTok API key
sessionId: "", //Replace with a generated Session ID
token: "", //Replace with a generated token (from the dashboard or using an OpenTok server SDK)
},
// A container can either be a query selector or an HTMLElement
streamContainers: function streamContainers(pubSub, type, data) {
return {
publisher: {
camera: '#cameraPublisherContainer',
},
subscriber: {
camera: '#cameraSubscriberContainer',
},
}[pubSub][type];
},
controlsContainer: '#controls',
packages: [],
communication: {
callProperites: null, // Using default
}
};
/** Application Logic */
const app = () => {
const state = {
connected: false,
active: false,
publishers: null,
subscribers: null,
meta: null,
localAudioEnabled: true,
localVideoEnabled: true,
};
/**
* Update the size and position of video containers based on the number of
* publishers and subscribers specified in the meta property returned by otCore.
*/
const updateVideoContainers = () => {
const { meta } = state;
const activeCameraSubscribers = meta ? meta.subscriber.camera : 0;
const videoContainerClass = `App-video-container ${''}`;
document.getElementById('appVideoContainer').setAttribute('class', videoContainerClass);
const cameraPublisherClass =
`video-container ${!!activeCameraSubscribers? 'small' : ''} ${!!activeCameraSubscribers? 'small' : ''}`;
document.getElementById('cameraPublisherContainer').setAttribute('class', cameraPublisherClass);
const cameraSubscriberClass =
`video-container ${!activeCameraSubscribers ? 'hidden' : ''} active-${activeCameraSubscribers}`;
document.getElementById('cameraSubscriberContainer').setAttribute('class', cameraSubscriberClass);
};
/**
* Update the UI
* @param {String} update - 'connected', 'active', or 'meta'
*/
const updateUI = (update) => {
const { connected, active } = state;
switch (update) {
case 'connected':
if (connected) {
document.getElementById('connecting-mask').classList.add('hidden');
document.getElementById('start-mask').classList.remove('hidden');
}
break;
case 'active':
if (active) {
document.getElementById('cameraPublisherContainer').classList.remove('hidden');
document.getElementById('start-mask').classList.add('hidden');
document.getElementById('controls').classList.remove('hidden');
}
else {
document.getElementById('start-mask').classList.remove('hidden');
document.getElementById('controls').classList.add('hidden');
document.getElementById('cameraPublisherContainer').classList.add('hidden');
document.getElementById('toggleLocalVideo').classList.remove('muted');
document.getElementById('toggleLocalAudio').classList.remove('muted');
}
break;
case 'meta':
updateVideoContainers();
break;
default:
console.log('nothing to do, nowhere to go');
}
};
/**
* Update the state and UI
*/
const updateState = (updates) => {
Object.assign(state, updates);
Object.keys(updates).forEach(update => updateUI(update));
};
/**
* Start publishing video/audio and subscribe to streams
*/
const startCall = () => {
otCore.startCall()
.then(({ publishers, subscribers, meta }) => {
updateState({ publishers, subscribers, meta, active: true });
}).catch(error => console.log(error));
};
/**
* Toggle publishing local audio
*/
const toggleLocalAudio = () => {
const enabled = state.localAudioEnabled;
otCore.toggleLocalAudio(!enabled);
updateState({ localAudioEnabled: !enabled });
const action = enabled ? 'add' : 'remove';
document.getElementById('toggleLocalAudio').classList[action]('muted');
};
/**
* Toggle publishing local video
*/
const toggleLocalVideo = () => {
const enabled = state.localVideoEnabled;
otCore.toggleLocalVideo(!enabled);
updateState({ localVideoEnabled: !enabled });
const action = enabled ? 'add' : 'remove';
document.getElementById('toggleLocalVideo').classList[action]('muted');
};
/**
* Toggle end call
*/
const toggleEndCall = () => {
updateState({ active: false });
otCore.endCall();
};
/**
* Subscribe to otCore and UI events
*/
const createEventListeners = () => {
const events = [
'subscribeToCamera',
'unsubscribeFromCamera',
];
events.forEach(event => otCore.on(event, ({ publishers, subscribers, meta }) => {
updateState({ publishers, subscribers, meta });
}));
document.getElementById('start').addEventListener('click', startCall);
document.getElementById('toggleLocalAudio').addEventListener('click', toggleLocalAudio);
document.getElementById('toggleLocalVideo').addEventListener('click', toggleLocalVideo);
document.getElementById('toggleEndCall').addEventListener('click', toggleEndCall);
};
/**
* Initialize otCore, connect to the session, and listen to events
*/
const init = () => {
otCore.init(options);
otCore.connect().then(() => updateState({ connected: true }));
createEventListeners();
};
init();
};
document.addEventListener('DOMContentLoaded', app);
================================================
FILE: js/public/js/components/opentok-acc-core.js
================================================
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
*/
var createPublisher = function createPublisher(publisherProperties) {
return new Promise(function (resolve, reject) {
// TODO: Handle adding 'name' option to props
var props = Object.assign({}, callProperties, publisherProperties);
// TODO: Figure out how to handle common vs package-specific options
// ^^^ This may already be available through package options
var container = dom.element(streamContainers('publisher', 'camera'));
var publisher = OT.initPublisher(container, props, function (error) {
error ? reject(error) : resolve(publisher);
});
});
};
/**
* Publish the local camera stream and update state
* @param {Object} publisherProperties
* @returns {Promise}
*/
var publish = function publish(publisherProperties) {
return new Promise(function (resolve, reject) {
var onPublish = function onPublish(publisher) {
return function (error) {
if (error) {
reject(error);
logAnalytics(logAction.startCall, logVariation.fail);
} else {
logAnalytics(logAction.startCall, logVariation.success);
state.addPublisher('camera', publisher);
resolve(publisher);
}
};
};
var publishToSession = function publishToSession(publisher) {
return session.publish(publisher, onPublish(publisher));
};
var handleError = function handleError(error) {
logAnalytics(logAction.startCall, logVariation.fail);
var errorMessage = error.code === 1010 ? 'Check your network connection' : error.message;
triggerEvent('error', errorMessage);
reject(error);
};
createPublisher(publisherProperties).then(publishToSession).catch(handleError);
});
};
/**
* Subscribe to a stream and update the state
* @param {Object} stream - An OpenTok stream object
* @returns {Promise}
*/
var subscribe = function subscribe(stream) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.subscribe, logVariation.attempt);
var streamMap = state.getStreamMap();
if (streamMap[stream.id]) {
// Are we already subscribing to the stream?
resolve();
} else {
(function () {
// No videoType indicates SIP https://tokbox.com/developer/guides/sip/
var type = pathOr('sip', 'videoType', stream);
var connectionData = JSON.parse(path(['connection', 'data'], stream) || null);
var container = dom.query(streamContainers('subscriber', type, connectionData));
var options = type === 'camera' ? callProperties : screenProperties;
var subscriber = session.subscribe(stream, container, options, function (error) {
if (error) {
logAnalytics(logAction.subscribe, logVariation.fail);
reject(error);
} else {
state.addSubscriber(subscriber);
triggerEvent('subscribeTo' + properCase(type), Object.assign({}, { subscriber: subscriber }, state.all()));
type === 'screen' && triggerEvent('startViewingSharedScreen', subscriber); // Legacy event
logAnalytics(logAction.subscribe, logVariation.success);
resolve();
}
});
})();
}
});
};
/**
* Unsubscribe from a stream and update the state
* @param {Object} subscriber - An OpenTok subscriber object
* @returns {Promise}
*/
var unsubscribe = function unsubscribe(subscriber) {
return new Promise(function (resolve) {
logAnalytics(logAction.unsubscribe, logVariation.attempt);
var type = path('stream.videoType', subscriber);
state.removeSubscriber(type, subscriber);
session.unsubscribe(subscriber);
logAnalytics(logAction.unsubscribe, logVariation.success);
resolve();
});
};
/**
* Ensure all required options are received
* @param {Object} options
*/
var validateOptions = function validateOptions(options) {
var requiredOptions = ['accPack'];
requiredOptions.forEach(function (option) {
if (!options[option]) {
throw new CoreError(option + ' is a required option.', 'invalidParameters');
}
});
accPack = options.accPack;
streamContainers = options.streamContainers;
callProperties = options.callProperties || defaultCallProperties;
connectionLimit = options.connectionLimit || null;
autoSubscribe = options.hasOwnProperty('autoSubscribe') ? options.autoSubscribe : true;
screenProperties = options.screenProperties || Object.assign({}, defaultCallProperties, { videoSource: 'window' });
};
/**
* Set session in module scope
*/
var setSession = function setSession() {
session = state.getSession();
};
/**
* Subscribe to new stream unless autoSubscribe is set to false
* @param {Object} stream
*/
var onStreamCreated = function onStreamCreated(_ref) {
var stream = _ref.stream;
return active && autoSubscribe && subscribe(stream);
};
/**
* Update state and trigger corresponding event(s) when stream is destroyed
* @param {Object} stream
*/
var onStreamDestroyed = function onStreamDestroyed(_ref2) {
var stream = _ref2.stream;
state.removeStream(stream);
var type = pathOr('sip', 'videoType', stream);
type === 'screen' && triggerEvent('endViewingSharedScreen'); // Legacy event
triggerEvent('unsubscribeFrom' + properCase(type), state.getPubSub());
};
/**
* Listen for API-level events
*/
var createEventListeners = function createEventListeners() {
accPack.on('streamCreated', onStreamCreated);
accPack.on('streamDestroyed', onStreamDestroyed);
};
/**
* Start publishing the local camera feed and subscribing to streams in the session
* @param {Object} publisherProperties
* @returns {Promise}
*/
var startCall = function startCall(publisherProperties) {
return new Promise(function (resolve, reject) {
// eslint-disable-line consistent-return
logAnalytics(logAction.startCall, logVariation.attempt);
/**
* Determine if we're able to join the session based on an existing connection limit
*/
if (!ableToJoin()) {
var errorMessage = 'Session has reached its connection limit';
triggerEvent('error', errorMessage);
logAnalytics(logAction.startCall, logVariation.fail);
return reject(new CoreError(errorMessage, 'connectionLimit'));
}
/**
* Subscribe to any streams that existed before we start the call from our side.
*/
var subscribeToInitialStreams = function subscribeToInitialStreams(publisher) {
// Get an array of initial subscription promises
var initialSubscriptions = function initialSubscriptions() {
if (autoSubscribe) {
var _ret2 = function () {
var streams = state.getStreams();
return {
v: Object.keys(streams).map(function (id) {
return subscribe(streams[id]);
})
};
}();
if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
}
return [Promise.resolve()];
};
// Handle success
var onSubscribeToAll = function onSubscribeToAll() {
var pubSubData = Object.assign({}, state.getPubSub(), { publisher: publisher });
triggerEvent('startCall', pubSubData);
active = true;
resolve(pubSubData);
};
// Handle error
var onError = function onError(reason) {
message('Failed to subscribe to all existing streams: ' + reason);
// We do not reject here in case we still successfully publish to the session
resolve(Object.assign({}, state.getPubSub(), { publisher: publisher }));
};
Promise.all(initialSubscriptions()).then(onSubscribeToAll).catch(onError);
};
publish(publisherProperties).then(subscribeToInitialStreams).catch(reject);
});
};
/**
* Stop publishing and unsubscribe from all streams
*/
var endCall = function endCall() {
logAnalytics(logAction.endCall, logVariation.attempt);
var _state$getPubSub = state.getPubSub(),
publishers = _state$getPubSub.publishers,
subscribers = _state$getPubSub.subscribers;
var unpublish = function unpublish(publisher) {
return session.unpublish(publisher);
};
Object.values(publishers.camera).forEach(unpublish);
Object.values(publishers.screen).forEach(unpublish);
// TODO Promise.all for unsubsribing
Object.values(subscribers.camera).forEach(unsubscribe);
Object.values(subscribers.screen).forEach(unsubscribe);
state.removeAllPublishers();
active = false;
triggerEvent('endCall');
logAnalytics(logAction.endCall, logVariation.success);
};
/**
* Enable/disable local audio or video
* @param {String} source - 'audio' or 'video'
* @param {Boolean} enable
*/
var enableLocalAV = function enableLocalAV(id, source, enable) {
var method = 'publish' + properCase(source);
var _state$getPubSub2 = state.getPubSub(),
publishers = _state$getPubSub2.publishers;
publishers.camera[id][method](enable);
};
/**
* Enable/disable remote audio or video
* @param {String} subscriberId
* @param {String} source - 'audio' or 'video'
* @param {Boolean} enable
*/
var enableRemoteAV = function enableRemoteAV(subscriberId, source, enable) {
var method = 'subscribeTo' + properCase(source);
var _state$getPubSub3 = state.getPubSub(),
subscribers = _state$getPubSub3.subscribers;
subscribers.camera[subscriberId][method](enable);
};
/**
* Initialize the communication component
* @param {Object} options
* @param {Object} options.accPack
* @param {Number} options.connectionLimit
* @param {Function} options.streamContainer
*/
var init = function init(options) {
return new Promise(function (resolve) {
validateOptions(options);
setSession();
createEventListeners();
resolve();
});
};
/** Exports */
module.exports = {
init: init,
startCall: startCall,
endCall: endCall,
subscribe: subscribe,
unsubscribe: unsubscribe,
enableLocalAV: enableLocalAV,
enableRemoteAV: enableRemoteAV
};
},{"./errors":4,"./logging":6,"./state":10,"./util":11}],3:[function(require,module,exports){
(function (global){
'use strict';
var _arguments = arguments;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
/* global OT */
/**
* Dependencies
*/
var util = require('./util');
var internalState = require('./state');
var accPackEvents = require('./events');
var communication = require('./communication');
var OpenTokSDK = require('./sdk-wrapper/sdkWrapper');
var _require = require('./errors'),
CoreError = _require.CoreError;
var _require2 = require('./logging'),
message = _require2.message,
initLogAnalytics = _require2.initLogAnalytics,
logAnalytics = _require2.logAnalytics,
logAction = _require2.logAction,
logVariation = _require2.logVariation,
updateLogAnalytics = _require2.updateLogAnalytics;
/**
* Helper methods
*/
var dom = util.dom,
path = util.path,
pathOr = util.pathOr,
properCase = util.properCase;
/**
* Individual Accelerator Packs
*/
var textChat = void 0; // eslint-disable-line no-unused-vars
var screenSharing = void 0; // eslint-disable-line no-unused-vars
var annotation = void 0;
var archiving = void 0; // eslint-disable-line no-unused-vars
/**
* Get access to an accelerator pack
* @param {String} packageName - textChat, screenSharing, annotation, or archiving
* @returns {Object} The instance of the accelerator pack
*/
var getAccPack = function getAccPack(packageName) {
logAnalytics(logAction.getAccPack, logVariation.attempt);
var packages = {
textChat: textChat,
screenSharing: screenSharing,
annotation: annotation,
archiving: archiving
};
logAnalytics(logAction.getAccPack, logVariation.success);
return packages[packageName];
};
/** Eventing */
var eventListeners = {};
/**
* Register events that can be listened to be other components/modules
* @param {array | string} events - A list of event names. A single event may
* also be passed as a string.
*/
var registerEvents = function registerEvents(events) {
var eventList = Array.isArray(events) ? events : [events];
eventList.forEach(function (event) {
if (!eventListeners[event]) {
eventListeners[event] = new Set();
}
});
};
/**
* Register a callback for a specific event or pass an object with
* with event => callback key/value pairs to register listeners for
* multiple events.
* @param {String | Object} event - The name of the event
* @param {Function} callback
*/
var on = function on(event, callback) {
// logAnalytics(logAction.on, logVariation.attempt);
if ((typeof event === 'undefined' ? 'undefined' : _typeof(event)) === 'object') {
Object.keys(event).forEach(function (eventName) {
on(eventName, event[eventName]);
});
return;
}
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
message(event + ' is not a registered event.');
// logAnalytics(logAction.on, logVariation.fail);
} else {
eventCallbacks.add(callback);
// logAnalytics(logAction.on, logVariation.success);
}
};
/**
* Remove a callback for a specific event. If no parameters are passed,
* all event listeners will be removed.
* @param {String} event - The name of the event
* @param {Function} callback
*/
var off = function off(event, callback) {
// logAnalytics(logAction.off, logVariation.attempt);
if (_arguments.lenth === 0) {
Object.keys(eventListeners).forEach(function (eventType) {
eventListeners[eventType].clear();
});
}
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
// logAnalytics(logAction.off, logVariation.fail);
message(event + ' is not a registered event.');
} else {
eventCallbacks.delete(callback);
// logAnalytics(logAction.off, logVariation.success);
}
};
/**
* Trigger an event and fire all registered callbacks
* @param {String} event - The name of the event
* @param {*} data - Data to be passed to callback functions
*/
var triggerEvent = function triggerEvent(event, data) {
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
registerEvents(event);
message(event + ' has been registered as a new event.');
} else {
eventCallbacks.forEach(function (callback) {
return callback(data, event);
});
}
};
/**
* Get the current OpenTok session object
* @returns {Object}
*/
var getSession = internalState.getSession;
/**
* Returns the current OpenTok session credentials
* @returns {Object}
*/
var getCredentials = internalState.getCredentials;
/**
* Returns the options used for initialization
* @returns {Object}
*/
var getOptions = internalState.getOptions;
var createEventListeners = function createEventListeners(session, options) {
Object.keys(accPackEvents).forEach(function (type) {
return registerEvents(accPackEvents[type]);
});
/**
* If using screen sharing + annotation in an external window, the screen sharing
* package will take care of calling annotation.start() and annotation.linkCanvas()
*/
var usingAnnotation = path('screenSharing.annotation', options);
var internalAnnotation = usingAnnotation && !path('screenSharing.externalWindow', options);
/**
* Wrap session events and update internalState when streams are created
* or destroyed
*/
accPackEvents.session.forEach(function (eventName) {
session.on(eventName, function (event) {
if (eventName === 'streamCreated') {
internalState.addStream(event.stream);
}
if (eventName === 'streamDestroyed') {
internalState.removeStream(event.stream);
}
triggerEvent(eventName, event);
});
});
if (usingAnnotation) {
on('subscribeToScreen', function (_ref) {
var subscriber = _ref.subscriber;
annotation.start(getSession()).then(function () {
var absoluteParent = dom.query(path('annotation.absoluteParent.subscriber', options));
var linkOptions = absoluteParent ? { absoluteParent: absoluteParent } : null;
annotation.linkCanvas(subscriber, subscriber.element.parentElement, linkOptions);
});
});
on('unsubscribeFromScreen', function () {
annotation.end();
});
}
on('startScreenSharing', function (publisher) {
internalState.addPublisher('screen', publisher);
triggerEvent('startScreenShare', Object.assign({}, { publisher: publisher }, internalState.getPubSub()));
if (internalAnnotation) {
annotation.start(getSession()).then(function () {
var absoluteParent = dom.query(path('annotation.absoluteParent.publisher', options));
var linkOptions = absoluteParent ? { absoluteParent: absoluteParent } : null;
annotation.linkCanvas(publisher, publisher.element.parentElement, linkOptions);
});
}
});
on('endScreenSharing', function (publisher) {
// delete publishers.screen[publisher.id];
internalState.removePublisher('screen', publisher);
triggerEvent('endScreenShare', internalState.getPubSub());
if (internalAnnotation) {
annotation.end();
}
});
};
var setupExternalAnnotation = function setupExternalAnnotation() {
return annotation.start(getSession(), {
screensharing: true
});
};
var linkAnnotation = function linkAnnotation(pubSub, annotationContainer, externalWindow) {
annotation.linkCanvas(pubSub, annotationContainer, {
externalWindow: externalWindow
});
if (externalWindow) {
(function () {
// Add subscribers to the external window
var streams = internalState.getStreams();
var cameraStreams = Object.keys(streams).reduce(function (acc, streamId) {
var stream = streams[streamId];
return stream.videoType === 'camera' ? acc.concat(stream) : acc;
}, []);
cameraStreams.forEach(annotation.addSubscriberToExternalWindow);
})();
}
};
var initPackages = function initPackages() {
logAnalytics(logAction.initPackages, logVariation.attempt);
var session = getSession();
var options = getOptions();
/**
* Try to require a package. If 'require' is unavailable, look for
* the package in global scope. A switch internalStatement is used because
* webpack and Browserify aren't able to resolve require internalStatements
* that use variable names.
* @param {String} packageName - The name of the npm package
* @param {String} globalName - The name of the package if exposed on global/window
* @returns {Object}
*/
var optionalRequire = function optionalRequire(packageName, globalName) {
var result = void 0;
/* eslint-disable global-require, import/no-extraneous-dependencies, import/no-unresolved */
try {
switch (packageName) {
case 'opentok-text-chat':
result = require('opentok-text-chat');
break;
case 'opentok-screen-sharing':
result = require('opentok-screen-sharing');
break;
case 'opentok-annotation':
result = require('opentok-annotation');
break;
case 'opentok-archiving':
result = require('opentok-archiving');
break;
default:
break;
}
/* eslint-enable global-require */
} catch (error) {
result = window[globalName];
}
if (!result) {
logAnalytics(logAction.initPackages, logVariation.fail);
throw new CoreError('Could not load ' + packageName, 'missingDependency');
}
return result;
};
var availablePackages = {
textChat: function textChat() {
return optionalRequire('opentok-text-chat', 'TextChatAccPack');
},
screenSharing: function screenSharing() {
return optionalRequire('opentok-screen-sharing', 'ScreenSharingAccPack');
},
annotation: function annotation() {
return optionalRequire('opentok-annotation', 'AnnotationAccPack');
},
archiving: function archiving() {
return optionalRequire('opentok-archiving', 'ArchivingAccPack');
}
};
var packages = {};
(path('packages', options) || []).forEach(function (acceleratorPack) {
if (availablePackages[acceleratorPack]) {
// eslint-disable-next-line no-param-reassign
packages[properCase(acceleratorPack)] = availablePackages[acceleratorPack]();
} else {
message(acceleratorPack + ' is not a valid accelerator pack');
}
});
/**
* Get containers for streams, controls, and the chat widget
*/
var getDefaultContainer = function getDefaultContainer(pubSub) {
return document.getElementById(pubSub + 'Container');
};
var getContainerElements = function getContainerElements() {
// Need to use path to check for null values
var controls = pathOr('#videoControls', 'controlsContainer', options);
var chat = pathOr('#chat', 'textChat.container', options);
var stream = pathOr(getDefaultContainer, 'streamContainers', options);
return { stream: stream, controls: controls, chat: chat };
};
/** *** *** *** *** */
/**
* Return options for the specified package
* @param {String} packageName
* @returns {Object}
*/
var packageOptions = function packageOptions(packageName) {
/**
* Methods to expose to accelerator packs
*/
var accPack = {
registerEventListener: on, // Legacy option
on: on,
registerEvents: registerEvents,
triggerEvent: triggerEvent,
setupExternalAnnotation: setupExternalAnnotation,
linkAnnotation: linkAnnotation
};
/**
* If options.controlsContainer/containers.controls is null,
* accelerator packs should not append their controls.
*/
var containers = getContainerElements();
var appendControl = !!containers.controls;
var controlsContainer = containers.controls; // Legacy option
var streamContainers = containers.stream;
var baseOptions = { session: session, accPack: accPack, controlsContainer: controlsContainer, appendControl: appendControl, streamContainers: streamContainers };
switch (packageName) {
/* beautify ignore:start */
case 'communication':
{
return Object.assign({}, baseOptions, options.communication);
}
case 'textChat':
{
var textChatOptions = {
textChatContainer: options.textChat.container,
waitingMessage: options.textChat.waitingMessage,
sender: { alias: options.textChat.name }
};
return Object.assign({}, baseOptions, textChatOptions);
}
case 'screenSharing':
{
var screenSharingContainer = { screenSharingContainer: streamContainers };
return Object.assign({}, baseOptions, screenSharingContainer, options.screenSharing);
}
case 'annotation':
{
return Object.assign({}, baseOptions, options.annotation);
}
case 'archiving':
{
return Object.assign({}, baseOptions, options.archiving);
}
default:
return {};
/* beautify ignore:end */
}
};
/** Create instances of each package */
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
communication.init(packageOptions('communication'));
textChat = packages.TextChat ? new packages.TextChat(packageOptions('textChat')) : null;
screenSharing = packages.ScreenSharing ? new packages.ScreenSharing(packageOptions('screenSharing')) : null;
annotation = packages.Annotation ? new packages.Annotation(packageOptions('annotation')) : null;
archiving = packages.Archiving ? new packages.Archiving(packageOptions('archiving')) : null;
logAnalytics(logAction.initPackages, logVariation.success);
};
/**
* Ensures that we have the required credentials
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
*/
var validateCredentials = function validateCredentials() {
var credentials = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var required = ['apiKey', 'sessionId', 'token'];
required.forEach(function (credential) {
if (!credentials[credential]) {
throw new CoreError(credential + ' is a required credential', 'invalidParameters');
}
});
};
/**
* Connect to the session
* @returns {Promise}
*/
var connect = function connect() {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.connect, logVariation.attempt);
var session = getSession();
var _getCredentials = getCredentials(),
token = _getCredentials.token;
session.connect(token, function (error) {
if (error) {
message(error);
logAnalytics(logAction.connect, logVariation.fail);
return reject(error);
}
var sessionId = session.sessionId,
apiKey = session.apiKey;
updateLogAnalytics(sessionId, path('connection.connectionId', session), apiKey);
logAnalytics(logAction.connect, logVariation.success);
initPackages();
triggerEvent('connected', session);
return resolve({ connections: session.connections.length() });
});
});
};
/**
* Disconnect from the session
* @returns {Promise}
*/
var disconnect = function disconnect() {
logAnalytics(logAction.disconnect, logVariation.attempt);
getSession().disconnect();
internalState.reset();
logAnalytics(logAction.disconnect, logVariation.success);
};
/**
* Force a remote connection to leave the session
* @param {Object} connection
* @returns {Promise}
*/
var forceDisconnect = function forceDisconnect(connection) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.forceDisconnect, logVariation.attempt);
getSession().forceDisconnect(connection, function (error) {
if (error) {
logAnalytics(logAction.forceDisconnect, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.forceDisconnect, logVariation.success);
resolve();
}
});
});
};
/**
* Force the publisher of a stream to stop publishing the stream
* @param {Object} stream
* @returns {Promise}
*/
var forceUnpublish = function forceUnpublish(stream) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.forceUnpublish, logVariation.attempt);
getSession().forceUnpublish(stream, function (error) {
if (error) {
logAnalytics(logAction.forceUnpublish, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.forceUnpublish, logVariation.success);
resolve();
}
});
});
};
/**
* Get the local publisher object for a stream
* @param {Object} stream - An OpenTok stream object
* @returns {Object} - The publisher object
*/
var getPublisherForStream = function getPublisherForStream(stream) {
return getSession().getPublisherForStream(stream);
};
/**
* Get the local subscriber objects for a stream
* @param {Object} stream - An OpenTok stream object
* @returns {Array} - An array of subscriber object
*/
var getSubscribersForStream = function getSubscribersForStream(stream) {
return getSession().getSubscribersForStream(stream);
};
/**
* Send a signal using the OpenTok signaling apiKey
* @param {String} type
* @param {*} [data]
* @param {Object} to - An OpenTok connection object
* @returns {Promise}
*/
var signal = function signal(type, signalData, to) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.signal, logVariation.attempt);
var session = getSession();
var data = JSON.stringify(signalData);
var signalObj = to ? { type: type, data: data, to: to } : { type: type, data: data };
session.signal(signalObj, function (error) {
if (error) {
logAnalytics(logAction.signal, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.signal, logVariation.success);
resolve();
}
});
});
};
/**
* Enable or disable local audio
* @param {Boolean} enable
*/
var toggleLocalAudio = function toggleLocalAudio(enable) {
logAnalytics(logAction.toggleLocalAudio, logVariation.attempt);
var _internalState$getPub = internalState.getPubSub(),
publishers = _internalState$getPub.publishers;
var toggleAudio = function toggleAudio(id) {
return communication.enableLocalAV(id, 'audio', enable);
};
Object.keys(publishers.camera).forEach(toggleAudio);
logAnalytics(logAction.toggleLocalAudio, logVariation.success);
};
/**
* Enable or disable local video
* @param {Boolean} enable
*/
var toggleLocalVideo = function toggleLocalVideo(enable) {
logAnalytics(logAction.toggleLocalVideo, logVariation.attempt);
var _internalState$getPub2 = internalState.getPubSub(),
publishers = _internalState$getPub2.publishers;
var toggleVideo = function toggleVideo(id) {
return communication.enableLocalAV(id, 'video', enable);
};
Object.keys(publishers.camera).forEach(toggleVideo);
logAnalytics(logAction.toggleLocalVideo, logVariation.success);
};
/**
* Enable or disable remote audio
* @param {String} id - Subscriber id
* @param {Boolean} enable
*/
var toggleRemoteAudio = function toggleRemoteAudio(id, enable) {
logAnalytics(logAction.toggleRemoteAudio, logVariation.attempt);
communication.enableRemoteAV(id, 'audio', enable);
logAnalytics(logAction.toggleRemoteAudio, logVariation.success);
};
/**
* Enable or disable remote video
* @param {String} id - Subscriber id
* @param {Boolean} enable
*/
var toggleRemoteVideo = function toggleRemoteVideo(id, enable) {
logAnalytics(logAction.toggleRemoteVideo, logVariation.attempt);
communication.enableRemoteAV(id, 'video', enable);
logAnalytics(logAction.toggleRemoteVideo, logVariation.success);
};
/**
* Initialize the accelerator pack
* @param {Object} options
* @param {Object} options.credentials
* @param {Array} [options.packages]
* @param {Object} [options.containers]
*/
var init = function init(options) {
if (!options) {
throw new CoreError('Missing options required for initialization', 'invalidParameters');
}
var credentials = options.credentials;
validateCredentials(options.credentials);
// Init analytics
initLogAnalytics(window.location.origin, credentials.sessionId, null, credentials.apiKey);
logAnalytics(logAction.init, logVariation.attempt);
var session = OT.initSession(credentials.apiKey, credentials.sessionId);
createEventListeners(session, options);
internalState.setSession(session);
internalState.setCredentials(credentials);
internalState.setOptions(options);
logAnalytics(logAction.init, logVariation.success);
};
var opentokCore = {
init: init,
connect: connect,
disconnect: disconnect,
forceDisconnect: forceDisconnect,
forceUnpublish: forceUnpublish,
getAccPack: getAccPack,
getOptions: getOptions,
getSession: getSession,
getPublisherForStream: getPublisherForStream,
getSubscribersForStream: getSubscribersForStream,
on: on,
off: off,
registerEventListener: on,
triggerEvent: triggerEvent,
signal: signal,
state: internalState.all,
startCall: communication.startCall,
endCall: communication.endCall,
OpenTokSDK: OpenTokSDK,
toggleLocalAudio: toggleLocalAudio,
toggleLocalVideo: toggleLocalVideo,
toggleRemoteAudio: toggleRemoteAudio,
toggleRemoteVideo: toggleRemoteVideo,
subscribe: communication.subscribe,
unsubscribe: communication.unsubscribe,
util: util
};
if (global === window) {
window.otCore = opentokCore;
}
module.exports = opentokCore;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./communication":2,"./errors":4,"./events":5,"./logging":6,"./sdk-wrapper/sdkWrapper":8,"./state":10,"./util":11,"opentok-annotation":undefined,"opentok-archiving":undefined,"opentok-screen-sharing":undefined,"opentok-text-chat":undefined}],4:[function(require,module,exports){
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/** Errors */
var CoreError = function (_Error) {
_inherits(CoreError, _Error);
function CoreError(errorMessage, errorType) {
_classCallCheck(this, CoreError);
var _this = _possibleConstructorReturn(this, (CoreError.__proto__ || Object.getPrototypeOf(CoreError)).call(this, "otAccCore: " + errorMessage));
_this.type = errorType;
return _this;
}
return CoreError;
}(Error);
module.exports = {
CoreError: CoreError
};
},{}],5:[function(require,module,exports){
'use strict';
var events = {
session: ['archiveStarted', 'archiveStopped', 'connectionCreated', 'connectionDestroyed', 'sessionConnected', 'sessionDisconnected', 'sessionReconnected', 'sessionReconnecting', 'signal', 'streamCreated', 'streamDestroyed', 'streamPropertyChanged'],
core: ['connected', 'startScreenShare', 'endScreenShare', 'error'],
communication: ['startCall', 'endCall', 'callPropertyChanged', 'subscribeToCamera', 'subscribeToScreen', 'subscribeToSip', 'unsubscribeFromCamera', 'unsubscribeFromScreen', 'startViewingSharedScreen', 'endViewingSharedScreen'],
textChat: ['showTextChat', 'hideTextChat', 'messageSent', 'errorSendingMessage', 'messageReceived'],
screenSharing: ['startScreenSharing', 'endScreenSharing', 'screenSharingError'],
annotation: ['startAnnotation', 'linkAnnotation', 'resizeCanvas', 'annotationWindowClosed', 'endAnnotation'],
archiving: ['startArchive', 'stopArchive', 'archiveReady', 'archiveError']
};
module.exports = events;
},{}],6:[function(require,module,exports){
'use strict';
var OTKAnalytics = require('opentok-solutions-logging');
// eslint-disable-next-line no-console
var message = function message(messageText) {
return console.log('otAccCore: ' + messageText);
};
/** Analytics */
var analytics = null;
var logVariation = {
attempt: 'Attempt',
success: 'Success',
fail: 'Fail'
};
var logAction = {
// vars for the analytics logs. Internal use
init: 'Init',
initPackages: 'InitPackages',
connect: 'ConnectCoreAcc',
disconnect: 'DisconnectCoreAcc',
forceDisconnect: 'ForceDisconnectCoreAcc',
forceUnpublish: 'ForceUnpublishCoreAcc',
getAccPack: 'GetAccPack',
signal: 'SignalCoreAcc',
startCall: 'StartCallCoreAcc',
endCall: 'EndCallCoreAcc',
toggleLocalAudio: 'ToggleLocalAudio',
toggleLocalVideo: 'ToggleLocalVideo',
toggleRemoteAudio: 'ToggleRemoteAudio',
toggleRemoteVideo: 'ToggleRemoteVideo',
subscribe: 'SubscribeCoreAcc',
unsubscribe: 'UnsubscribeCoreAcc'
};
var updateLogAnalytics = function updateLogAnalytics(sessionId, connectionId, apiKey) {
if (sessionId && connectionId && apiKey) {
var sessionInfo = {
sessionId: sessionId,
connectionId: connectionId,
partnerId: apiKey
};
analytics.addSessionInfo(sessionInfo);
}
};
var initLogAnalytics = function initLogAnalytics(source, sessionId, connectionId, apikey) {
var otkanalyticsData = {
clientVersion: 'js-vsol-1.0.0',
source: source,
componentId: 'coreAccelerator',
name: 'coreAccelerator',
partnerId: apikey
};
analytics = new OTKAnalytics(otkanalyticsData);
if (connectionId) {
updateLogAnalytics(sessionId, connectionId, apikey);
}
};
var logAnalytics = function logAnalytics(action, variation) {
analytics.logEvent({ action: action, variation: variation });
};
module.exports = {
message: message,
logAction: logAction,
logVariation: logVariation,
initLogAnalytics: initLogAnalytics,
updateLogAnalytics: updateLogAnalytics,
logAnalytics: logAnalytics
};
},{"opentok-solutions-logging":1}],7:[function(require,module,exports){
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/** Errors */
var SDKError = function (_Error) {
_inherits(SDKError, _Error);
function SDKError(errorMessage, errorType) {
_classCallCheck(this, SDKError);
var _this = _possibleConstructorReturn(this, (SDKError.__proto__ || Object.getPrototypeOf(SDKError)).call(this, "otSDK: " + errorMessage));
_this.type = errorType;
return _this;
}
return SDKError;
}(Error);
module.exports = {
SDKError: SDKError
};
},{}],8:[function(require,module,exports){
(function (global){
'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/* global OT */
/* Dependencies */
var State = require('./state');
var _require = require('./errors'),
SDKError = _require.SDKError;
/* Internal variables */
var stateMap = new WeakMap();
/* Internal methods */
/**
* Ensures that we have the required credentials
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
* @returns {Object}
*/
var validateCredentials = function validateCredentials() {
var credentials = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var required = ['apiKey', 'sessionId', 'token'];
required.forEach(function (credential) {
if (!credentials[credential]) {
throw new SDKError(credential + ' is a required credential', 'invalidParameters');
}
});
return credentials;
};
/**
* Initialize an OpenTok publisher object
* @param {String | Object} element - The target element
* @param {Object} properties - The publisher properties
* @returns {Promise}
*/
var initPublisher = function initPublisher(element, properties) {
return new Promise(function (resolve, reject) {
var publisher = OT.initPublisher(element, properties, function (error) {
error ? reject(error) : resolve(publisher);
});
});
};
/**
* Binds and sets a single event listener on the OpenTok session
* @param {String} event - The name of the event
* @param {Function} callback
*/
var bindListener = function bindListener(target, context, event, callback) {
var paramsError = '\'on\' requires a string and a function to create an event listener.';
if (typeof event !== 'string' || typeof callback !== 'function') {
throw new SDKError(paramsError, 'invalidParameters');
}
target.on(event, callback.bind(context));
};
/**
* Bind and set event listeners
* @param {Object} target - An OpenTok session, publisher, or subscriber object
* @param {Object} context - The context to which to bind event listeners
* @param {Object | Array} listeners - An object (or array of objects) with
* eventName/callback k/v pairs
*/
var bindListeners = function bindListeners(target, context, listeners) {
/**
* Create listeners from an object with event/callback k/v pairs
* @param {Object} listeners
*/
var createListenersFromObject = function createListenersFromObject(eventListeners) {
Object.keys(eventListeners).forEach(function (event) {
bindListener(target, context, event, eventListeners[event]);
});
};
if (Array.isArray(listeners)) {
listeners.forEach(function (listener) {
return createListenersFromObject(listener);
});
} else {
createListenersFromObject(listeners);
}
};
/**
* @class
* Represents an OpenTok SDK Wrapper
*/
var OpenTokSDK = function () {
/**
* Create an SDK Wrapper
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
*/
function OpenTokSDK(credentials) {
_classCallCheck(this, OpenTokSDK);
this.credentials = validateCredentials(credentials);
stateMap.set(this, new State());
this.session = OT.initSession(credentials.apiKey, credentials.sessionId);
this.setInternalListeners();
}
/**
* Determines if a connection object is my local connection
* @param {Object} connection - An OpenTok connection object
* @returns {Boolean}
*/
_createClass(OpenTokSDK, [{
key: 'isMe',
value: function isMe(connection) {
var session = this.session;
return session && session.connection.connectionId === connection.connectionId;
}
/**
* Wrap OpenTok session events
*/
}, {
key: 'setInternalListeners',
value: function setInternalListeners() {
/**
* Wrap session events and update state when streams are created
* or destroyed
*/
var state = stateMap.get(this);
this.session.on('streamCreated', function (_ref) {
var stream = _ref.stream;
return state.addStream(stream);
});
this.session.on('streamDestroyed', function (_ref2) {
var stream = _ref2.stream;
return state.removeStream(stream);
});
}
/**
* Register a callback for a specific event, pass an object
* with event => callback key/values (or an array of objects)
* to register callbacks for multiple events.
* @param {String | Object | Array} [events] - The name of the events
* @param {Function} [callback]
* https://tokbox.com/developer/sdks/js/reference/Session.html#on
*/
}, {
key: 'on',
value: function on() {
if (arguments.length === 1 && _typeof(arguments.length <= 0 ? undefined : arguments[0]) === 'object') {
bindListeners(this.session, this, arguments.length <= 0 ? undefined : arguments[0]);
} else if (arguments.length === 2) {
bindListener(this.session, this, arguments.length <= 0 ? undefined : arguments[0], arguments.length <= 1 ? undefined : arguments[1]);
}
}
/**
* Remove a callback for a specific event. If no parameters are passed,
* all callbacks for the session will be removed.
* @param {String} [events] - The name of the events
* @param {Function} [callback]
* https://tokbox.com/developer/sdks/js/reference/Session.html#off
*/
}, {
key: 'off',
value: function off() {
var _session;
(_session = this.session).off.apply(_session, arguments);
}
/**
* Enable or disable local publisher audio
* @param {Boolean} enable
*/
}, {
key: 'enablePublisherAudio',
value: function enablePublisherAudio(enable) {
var _stateMap$get$getPubS = stateMap.get(this).getPubSub(),
publishers = _stateMap$get$getPubS.publishers;
Object.keys(publishers.camera).forEach(function (publisherId) {
publishers.camera[publisherId].publishAudio(enable);
});
}
/**
* Enable or disable local publisher video
* @param {Boolean} enable
*/
}, {
key: 'enablePublisherVideo',
value: function enablePublisherVideo(enable) {
var _stateMap$get$getPubS2 = stateMap.get(this).getPubSub(),
publishers = _stateMap$get$getPubS2.publishers;
Object.keys(publishers.camera).forEach(function (publisherId) {
publishers.camera[publisherId].publishVideo(enable);
});
}
/**
* Enable or disable local subscriber audio
* @param {String} streamId
* @param {Boolean} enable
*/
}, {
key: 'enableSubscriberAudio',
value: function enableSubscriberAudio(streamId, enable) {
var _stateMap$get$all = stateMap.get(this).all(),
streamMap = _stateMap$get$all.streamMap,
subscribers = _stateMap$get$all.subscribers;
var subscriberId = streamMap[streamId];
var subscriber = subscribers.camera[subscriberId] || subscribers.screen[subscriberId];
subscriber && subscriber.subscribeToVideo(enable);
}
/**
* Enable or disable local subscriber video
* @param {String} streamId
* @param {Boolean} enable
*/
}, {
key: 'enableSubscriberVideo',
value: function enableSubscriberVideo(streamId, enable) {
var _stateMap$get$all2 = stateMap.get(this).all(),
streamMap = _stateMap$get$all2.streamMap,
subscribers = _stateMap$get$all2.subscribers;
var subscriberId = streamMap[streamId];
var subscriber = subscribers.camera[subscriberId] || subscribers.screen[subscriberId];
subscriber && subscriber.subscribeToAudio(enable);
}
/**
* Create and publish a stream
* @param {String | Object} element - The target element
* @param {Object} properties - The publisher properties
* @param {Array | Object} [eventListeners] - An object (or array of objects) with
* eventName/callback k/v pairs
* @param {Boolean} [preview] - Create a publisher with publishing to the session
* @returns {Promise}
*/
}, {
key: 'publish',
value: function publish(element, properties) {
var _this = this;
var eventListeners = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var preview = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
return new Promise(function (resolve, reject) {
initPublisher(element, properties) // eslint-disable-next-line no-confusing-arrow
.then(function (publisher) {
eventListeners && bindListeners(publisher, _this, eventListeners);
if (preview) {
resolve(publisher);
} else {
_this.publishPreview(publisher).then(resolve).catch(reject);
}
}).catch(reject);
});
}
/**
* Publish a 'preview' stream to the session
* @param {Object} publisher - An OpenTok publisher object
* @returns {Promise}
*/
}, {
key: 'publishPreview',
value: function publishPreview(publisher) {
var _this2 = this;
return new Promise(function (resolve, reject) {
var state = stateMap.get(_this2);
_this2.session.publish(publisher, function (error) {
error && reject(error);
var type = publisher.stream.videoType;
state.addPublisher(type, publisher);
resolve(publisher);
});
});
}
/**
* Stop publishing a stream
* @param {Object} publisher - An OpenTok publisher object
*/
}, {
key: 'unpublish',
value: function unpublish(publisher) {
var type = publisher.stream.videoType;
var state = stateMap.get(this);
this.session.unpublish(publisher);
state.removePublisher(type, publisher);
}
/**
* Subscribe to stream
* @param {Object} stream
* @param {String | Object} container - The id of the container or a reference to the element
* @param {Object} [properties]
* @param {Array | Object} [eventListeners] - An object (or array of objects) with
* eventName/callback k/v pairs
* @returns {Promise}
* https://tokbox.com/developer/sdks/js/reference/Session.html#subscribe
*/
}, {
key: 'subscribe',
value: function subscribe(stream, container, properties, eventListeners) {
var _this3 = this;
var state = stateMap.get(this);
return new Promise(function (resolve, reject) {
var subscriber = _this3.session.subscribe(stream, container, properties, function (error) {
if (error) {
reject(error);
} else {
state.addSubscriber(subscriber);
eventListeners && bindListeners(subscriber, _this3, eventListeners);
resolve(subscriber);
}
});
});
}
/**
* Unsubscribe from a stream and update the state
* @param {Object} subscriber - An OpenTok subscriber object
* @returns {Promise}
*/
}, {
key: 'unsubscribe',
value: function unsubscribe(subscriber) {
var _this4 = this;
var state = stateMap.get(this);
return new Promise(function (resolve) {
_this4.session.unsubscribe(subscriber);
state.removeSubscriber(subscriber);
resolve();
});
}
/**
* Connect to the OpenTok session
* @param {Array | Object} [eventListeners] - An object (or array of objects) with
* eventName/callback k/v pairs
* @returns {Promise}
*/
}, {
key: 'connect',
value: function connect(eventListeners) {
var _this5 = this;
this.off();
eventListeners && this.on(eventListeners);
return new Promise(function (resolve, reject) {
var token = _this5.credentials.token;
_this5.session.connect(token, function (error) {
error ? reject(error) : resolve();
});
});
}
/**
* Force a remote connection to leave the session
* @param {Object} connection
* @returns {Promise}
*/
}, {
key: 'forceDisconnect',
value: function forceDisconnect(connection) {
var _this6 = this;
return new Promise(function (resolve, reject) {
_this6.session.forceDisconnect(connection, function (error) {
error ? reject(error) : resolve();
});
});
}
/**
* Force the publisher of a stream to stop publishing the stream
* @param {Object} stream
* @returns {Promise}
*/
}, {
key: 'forceUnpublish',
value: function forceUnpublish(stream) {
var _this7 = this;
return new Promise(function (resolve, reject) {
_this7.session.forceUnpublish(stream, function (error) {
error ? reject(error) : resolve();
});
});
}
/**
* Send a signal using the OpenTok signaling apiKey
* @param {String} type
* @param {*} signalData
* @param {Object} [to] - An OpenTok connection object
* @returns {Promise}
* https://tokbox.com/developer/guides/signaling/js/
*/
}, {
key: 'signal',
value: function signal(type, signalData, to) {
var _this8 = this;
var data = JSON.stringify(signalData);
var signal = to ? { type: type, data: data, to: to } : { type: type, data: data };
return new Promise(function (resolve, reject) {
_this8.session.signal(signal, function (error) {
error ? reject(error) : resolve();
});
});
}
/**
* Disconnect from the OpenTok session
*/
}, {
key: 'disconnect',
value: function disconnect() {
this.session.disconnect();
stateMap.get(this).reset();
}
/**
* Return the state of the OpenTok session
* @returns {Object} Streams, publishers, subscribers, and stream map
*/
}, {
key: 'state',
value: function state() {
return stateMap.get(this).all();
}
}]);
return OpenTokSDK;
}();
if (global === window) {
window.OpenTokSDK = OpenTokSDK;
}
module.exports = OpenTokSDK;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./errors":7,"./state":9}],9:[function(require,module,exports){
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var State = function () {
function State() {
_classCallCheck(this, State);
this.publishers = {
camera: {},
screen: {}
};
this.subscribers = {
camera: {},
screen: {}
};
this.streams = {};
// Map stream ids to subscriber/publisher ids
this.streamMap = {};
// OpenTok session
this.session = null;
// OpenTok credentials
this.credentials = null;
}
// Get the current OpenTok session
_createClass(State, [{
key: "getSession",
value: function getSession() {
return this.session;
}
// Set the current OpenTok session
}, {
key: "setSession",
value: function setSession(session) {
this.session = session;
}
// Get the current OpenTok credentials
}, {
key: "getCredentials",
value: function getCredentials() {
return this.credentials;
}
// Set the current OpenTok credentials
}, {
key: "setCredentials",
value: function setCredentials(credentials) {
this.credentials = credentials;
}
/**
* Returns the count of current publishers and subscribers by type
* @retuns {Object}
* {
* publishers: {
* camera: 1,
* screen: 1,
* total: 2
* },
* subscribers: {
* camera: 3,
* screen: 1,
* total: 4
* }
* }
*/
}, {
key: "pubSubCount",
value: function pubSubCount() {
var publishers = this.publishers,
subscribers = this.subscribers;
/* eslint-disable no-param-reassign */
var pubs = Object.keys(publishers).reduce(function (acc, source) {
acc[source] = Object.keys(publishers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
var subs = Object.keys(subscribers).reduce(function (acc, source) {
acc[source] = Object.keys(subscribers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
/* eslint-enable no-param-reassign */
return { publisher: pubs, subscriber: subs };
}
/**
* Returns the current publishers and subscribers, along with a count of each
*/
}, {
key: "getPubSub",
value: function getPubSub() {
var publishers = this.publishers,
subscribers = this.subscribers;
return { publishers: publishers, subscribers: subscribers, meta: this.pubSubCount() };
}
}, {
key: "addPublisher",
value: function addPublisher(type, publisher) {
this.streamMap[publisher.streamId] = publisher.id;
this.publishers[type][publisher.id] = publisher;
}
}, {
key: "removePublisher",
value: function removePublisher(type, publisher) {
var id = publisher.id || this.streamMap[publisher.streamId];
delete this.publishers[type][id];
}
}, {
key: "removeAllPublishers",
value: function removeAllPublishers() {
this.publishers.camera = {};
this.publishers.screen = {};
}
}, {
key: "addSubscriber",
value: function addSubscriber(subscriber) {
var type = subscriber.stream.videoType;
var streamId = subscriber.stream.id;
this.subscribers[type][subscriber.id] = subscriber;
this.streamMap[streamId] = subscriber.id;
}
}, {
key: "removeSubscriber",
value: function removeSubscriber() {
var subscriber = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var stream = subscriber.stream;
var type = stream && stream.videoType;
delete this.subscribers[type][subscriber.id];
}
}, {
key: "addStream",
value: function addStream(stream) {
this.streams[stream.id] = stream;
}
}, {
key: "removeStream",
value: function removeStream(stream) {
var type = stream.videoType;
var subscriberId = this.streamMap[stream.id];
delete this.streamMap[stream.id];
delete this.streams[stream.id];
this.removeSubscriber(this.subscribers[type][subscriberId]);
}
}, {
key: "getStreams",
value: function getStreams() {
return this.streams;
}
/** Reset streams, publishers, and subscribers */
}, {
key: "reset",
value: function reset() {
this.streams = {};
this.streamMap = {};
this.publishers = { camera: {}, screen: {} };
this.subscribers = { camera: {}, screen: {} };
}
}, {
key: "all",
value: function all() {
var streams = this.streams,
streamMap = this.streamMap;
return Object.assign({}, this.getPubSub(), { streams: streams, streamMap: streamMap });
}
}]);
return State;
}();
module.exports = State;
},{}],10:[function(require,module,exports){
'use strict';
/**
* Internal variables
*/
// Map publisher ids to publisher objects
var publishers = {
camera: {},
screen: {}
};
// Map subscriber id to subscriber objects
var subscribers = {
camera: {},
screen: {},
sip: {}
};
// Map stream ids to stream objects
var streams = {};
// Map stream ids to subscriber/publisher ids
var streamMap = {};
var session = null;
var credentials = null;
var options = null;
/**
* Internal methods
*/
/**
* Returns the count of current publishers and subscribers by type
* @retuns {Object}
* {
* publishers: {
* camera: 1,
* screen: 1,
* total: 2
* },
* subscribers: {
* camera: 3,
* screen: 1,
* total: 4
* }
* }
*/
var pubSubCount = function pubSubCount() {
/* eslint-disable no-param-reassign */
var pubs = Object.keys(publishers).reduce(function (acc, source) {
acc[source] = Object.keys(publishers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
var subs = Object.keys(subscribers).reduce(function (acc, source) {
acc[source] = Object.keys(subscribers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, sip: 0, total: 0 });
/* eslint-enable no-param-reassign */
return { publisher: pubs, subscriber: subs };
};
/**
* Returns the current publishers and subscribers, along with a count of each
* @returns {Object}
*/
var getPubSub = function getPubSub() {
return { publishers: publishers, subscribers: subscribers, meta: pubSubCount() };
};
/**
* Get streams, streamMap, publishers, and subscribers
* @return {Object}
*/
var all = function all() {
return Object.assign({}, { streams: streams, streamMap: streamMap }, getPubSub());
};
/**
* Get the current OpenTok session
* @returns {Object}
*/
var getSession = function getSession() {
return session;
};
/**
* Set the current OpenTok session
* @param {Object} otSession
*/
var setSession = function setSession(otSession) {
session = otSession;
};
/**
* Get the current OpenTok credentials
* @returns {Object}
*/
var getCredentials = function getCredentials() {
return credentials;
};
/**
* Set the current OpenTok credentials
* @param {Object} otCredentials
*/
var setCredentials = function setCredentials(otCredentials) {
credentials = otCredentials;
};
/**
* Get the options defined for core
* @returns {Object}
*/
var getOptions = function getOptions() {
return options;
};
/**
* Set the options defined for core
* @param {Object} otOptions
*/
var setOptions = function setOptions(otOptions) {
options = otOptions;
};
/**
* Add a stream to state
* @param {Object} stream - An OpenTok stream object
*/
var addStream = function addStream(stream) {
streams[stream.id] = stream;
};
/**
* Remove a stream from state and any associated subscribers
* @param {Object} stream - An OpenTok stream object
*/
var removeStream = function removeStream(stream) {
var type = stream.videoType;
var subscriberId = streamMap[stream.id];
delete streamMap[stream.id];
delete subscribers[type][subscriberId];
delete streams[stream.id];
};
/**
* Get all remote streams
* @returns {Object}
*/
var getStreams = function getStreams() {
return streams;
};
/**
* Get the map of stream ids to publisher/subscriber ids
* @returns {Object}
*/
var getStreamMap = function getStreamMap() {
return streamMap;
};
/**
* Add a publisher to state
* @param {String} type - 'camera' or 'screen'
* @param {Object} publisher - The OpenTok publisher object
*/
var addPublisher = function addPublisher(type, publisher) {
streamMap[publisher.streamId] = publisher.id;
publishers[type][publisher.id] = publisher;
};
/**
* Remove a publisher from state
* @param {String} type - 'camera' or 'screen'
* @param {Object} publisher - The OpenTok publisher object
*/
var removePublisher = function removePublisher(type, publisher) {
var id = publisher.id || streamMap[publisher.streamId];
delete publishers[type][id];
delete streamMap[publisher.streamId];
};
/**
* Remove all publishers from state
*/
var removeAllPublishers = function removeAllPublishers() {
['camera', 'screen'].forEach(function (type) {
Object.values(publishers[type]).forEach(function (publisher) {
removePublisher(type, publisher);
});
});
};
/**
* Add a subscriber to state
* @param {Object} - An OpenTok subscriber object
*/
var addSubscriber = function addSubscriber(subscriber) {
var type = subscriber.stream.videoType;
var streamId = subscriber.stream.id;
subscribers[type][subscriber.id] = subscriber;
streamMap[streamId] = subscriber.id;
};
/**
* Remove a publisher from state
* @param {String} type - 'camera' or 'screen'
* @param {Object} subscriber - The OpenTok subscriber object
*/
var removeSubscriber = function removeSubscriber(type, subscriber) {
var id = subscriber.id || streamMap[subscriber.streamId];
delete subscribers[type][id];
delete streamMap[subscriber.streamId];
};
/**
* Remove all subscribers from state
*/
var removeAllSubscribers = function removeAllSubscribers() {
['camera', 'screen', 'sip'].forEach(function (type) {
Object.values(subscribers[type]).forEach(function (subscriber) {
removeSubscriber(type, subscriber);
});
});
};
/**
* Reset state
*/
var reset = function reset() {
removeAllPublishers();
removeAllSubscribers();
[streams, streamMap].forEach(function (streamObj) {
Object.keys(streamObj).forEach(function (streamId) {
delete streamObj[streamId]; // eslint-disable-line no-param-reassign
});
});
};
/** Exports */
module.exports = {
all: all,
getSession: getSession,
setSession: setSession,
getCredentials: getCredentials,
setCredentials: setCredentials,
getOptions: getOptions,
setOptions: setOptions,
addStream: addStream,
removeStream: removeStream,
getStreams: getStreams,
getStreamMap: getStreamMap,
addPublisher: addPublisher,
removePublisher: removePublisher,
removeAllPublishers: removeAllPublishers,
addSubscriber: addSubscriber,
removeSubscriber: removeSubscriber,
removeAllSubscribers: removeAllSubscribers,
getPubSub: getPubSub,
reset: reset
};
},{}],11:[function(require,module,exports){
'use strict';
/** Wrap DOM selector methods:
* document.querySelector,
* document.getElementById,
* document.getElementsByClassName
* 'element' checks for a string before returning an element with `query`
*/
var dom = {
query: function query(arg) {
return document.querySelector(arg);
},
id: function id(arg) {
return document.getElementById(arg);
},
class: function _class(arg) {
return document.getElementsByClassName(arg);
},
element: function element(el) {
return typeof el === 'string' ? this.query(el) : el;
}
};
/**
* Returns a (nested) propery from an object, or undefined if it doesn't exist
* @param {String | Array} props - An array of properties or a single property
* @param {Object | Array} obj
*/
var path = function path(props, obj) {
var nested = obj;
var properties = typeof props === 'string' ? props.split('.') : props;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = properties[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var property = _step.value;
nested = nested[property];
if (nested === undefined) {
return nested;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return nested;
};
/**
* Checks for a (nested) propery in an object and returns the property if
* it exists. Otherwise, it returns a default value.
* @param {*} d - Default value
* @param {String | Array} props - An array of properties or a single property
* @param {Object | Array} obj
*/
var pathOr = function pathOr(d, props, obj) {
var value = path(props, obj);
return value === undefined ? d : value;
};
/**
* Converts a string to proper case (e.g. 'camera' => 'Camera')
* @param {String} text
* @returns {String}
*/
var properCase = function properCase(text) {
return '' + text[0].toUpperCase() + text.slice(1);
};
module.exports = {
dom: dom,
path: path,
pathOr: pathOr,
properCase: properCase
};
},{}]},{},[3]);
================================================
FILE: js/server.js
================================================
/* eslint-env es6 */
/*
* Dependencies
*/
const express = require('express');
const bodyParser = require('body-parser');
/*
* Config
*/
const app = express();
const port = process.env.PORT || 8080;
app.use(express.static(`${__dirname}/public`));
app.use(bodyParser.json());
/*
* User Routes
*/
app.get('/', (req, res) => {
res.sendfile('public/index.html');
});
/*
* Listen
*/
app.listen(process.env.PORT || port);
console.log(`app listening on port ${port}`);