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 Tokbox is now known as Vonage > 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-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 ================================================ ![logo](../tokbox-logo.png) # 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 ================================================ ![logo](../tokbox-logo.png) # 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 ================================================ ![logo](../tokbox-logo.png) # 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

OpenTok Accelerator Core

Connecting
================================================ 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}`);