├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .jsbeautifyrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── css └── screen-share.css ├── images ├── icon │ ├── screen-share.png │ ├── screen-share@2x.png │ ├── screenshare-large.png │ └── screenshare-large@2x.png └── main │ └── mask.png ├── karma.conf.js ├── package.json ├── src ├── detect-browser.js └── opentok-screen-sharing.js └── test ├── components └── accelerator-pack.js ├── index.html └── opentok-screen-sharing-test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 5 9 | }, 10 | "plugins": [ 11 | "react" 12 | ], 13 | "rules": { 14 | "no-underscore-dangle": 0, 15 | "vars-on-top": 0, 16 | "padded-blocks": 0, 17 | "no-var": 0, 18 | "strict": 0, 19 | "comma-dangle": 0, 20 | "func-names": 0, 21 | "prefer-arrow-callback": 0, 22 | "object-shorthand": 0, 23 | "new-cap": [2, { "properties": false }], 24 | "no-unused-expressions": [2, { "allowTernary": true, "allowShortCircuit": true }], 25 | "global-require": 0 26 | }, 27 | "globals": { 28 | "$": true, 29 | "_": true 30 | } 31 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## New issue checklist 2 | 3 | 4 | - [ ] I have read all of the [`README`](/README.md) 5 | - [ ] I have searched [existing issues](https://github.com/opentok/accelerator-screen-sharing-js/issues?q=is%3Aissue+sort%3Acreated-desc) and **this is not a duplicate**. 6 | 7 | ### General information 8 | 9 | - Library version(s): 10 | - iOS/Android/Browser version(s): 11 | - Devices/Simulators/Machine affected: 12 | - Reproducible in the demo project? (Yes/No): 13 | - Related issues: 14 | 15 | ## Bug report 16 | 17 | #### Expected behavior 18 | 19 | > ... 20 | 21 | #### Actual behavior 22 | 23 | > ... 24 | 25 | #### Steps to reproduce 26 | 27 | > ... 28 | 29 | #### Crash log? Screenshots? Videos? Sample project? 30 | 31 | >... 32 | 33 | ## Question or Feature Request 34 | 35 | > ... 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull request checklist 2 | 3 | - [ ] All tests pass. Demo project builds and runs. 4 | - [ ] I have resolved any merge conflicts. Confirmation: ____ 5 | 6 | #### This fixes issue #___. 7 | 8 | ## What's in this pull request? 9 | 10 | >... 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Aggregit 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | recordMetrics: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: michaeljolley/aggregit@v1.3 12 | with: 13 | githubToken: ${{ secrets.GITHUB_TOKEN }} 14 | project_id: ${{ secrets.project_id }} 15 | private_key: ${{ secrets.private_key }} 16 | client_email: ${{ secrets.client_email }} 17 | firebaseDbUrl: ${{ secrets.firebaseDbUrl }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | test/coverage 5 | 6 | package-lock.json 7 | debug.log -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "js":{ 3 | "indent_size": 2, 4 | "space_after_anon_function": true, 5 | "end_with_newline": true, 6 | "brace_style": "collapse-preserve-inline" 7 | } 8 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10.16' 4 | services: 5 | - xvfb 6 | branches: 7 | only: 8 | - main 9 | before_script: 10 | - npm install 11 | script: 12 | - npm run build 13 | - npm test 14 | - VER_DEPL=$(npm show opentok-screen-sharing version); 15 | - VERSION=$(jq '.version' package.json | sed -e 's/^"//' -e 's/"$//'); 16 | - if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_BRANCH" == "main" && "$VER_DEPL" != "$VERSION" ]]; then 17 | git commit -m "New build. Bump npm version." --allow-empty; 18 | git tag v$VERSION; 19 | git push --quiet https://$GITHUBKEY@github.com/opentok/accelerator-screen-sharing-js v$VERSION; 20 | fi 21 | deploy: 22 | provider: npm 23 | email: vsolutions@tokbox.com 24 | api_key: $NPM_TOKEN 25 | on: 26 | branch: main 27 | condition: $VERSION != $VER_DEPL 28 | skip_cleanup: true 29 | notifications: 30 | slack: 31 | secure: uyXK9zqfENTTYdP5ghoi3mRVv5aWxp6yj80MRP0qMTubtnOSvye0kCyY7Y/u8Ww+wE+9Xw2ZD6qLt0JEbO6HUAOOaXd6JsduxTwS4m1L/4qzPvBQztDAJRNvQx3nbh38uP0fuzZc1GRfTPNmDYUvczPRcWTduPZO82Kgzb4vafkss4thfKbnZqwLud/pghaJuN/LeESO2vmf0HxUlxTpnw4BYBUAnPo/9YUao7bvg2/3kxIpxApKZyxnoHeBcoNOOzzCmVBQnkfc4LMbXXg7ixwe0789gzOmr7ifeSBN6FVwNrw0rUNGQHA5tKnOclsfvHwdGHPoOLadZqbMi1QYXT+t89WpveBsbhBu3xqzZzHr7+LoBaP5Heg+FIeCyNqk7H1z24/7HtEDwc1dwIuGkphWYdHazSCNcjet1mVsVouM7Yg5SuUNN+pnAo74A5b2iR/PS7RoU09xAclAThwVV/txkz42nOvXka8DHKXEn8Kie8xplWt2T8aDq2jfHpI9I27/mxZl8GxqjPCbMCl9I1BE8FnnQP6FoFhj7EQ8+CYbxCA45XOYV0guv8D34/lTXmh2QozAxD3R/BOqaOn6V2aIb2szDb4KeM5t60qCGS6tuW7/hTt4xTcrrnfrfNDntXO2RkHnxONpuEGk0O1dLzL0Hdf8irq3vqUwE+Sk7GE= 32 | addons: 33 | jwt: 34 | secure: aOwcPFBA5QVD7PfbyJ/Vm9f5oAbQECCrrYkViFe/Vt228V3/aUHvlGKr7n9hQkCOmTXhF4jpUXkji/1Qa0aZl1mRZI6qh3d5HpmEn5BxcwUAdOkJ+7ldIKyg4ZoSEEPRC1Hn6v4IXG41fH0yRRaiQOHrLUc/AI2P4Vk9PFJyeXe0aCv2jT9AIy6z1r0ez5GZWjOTIX+1+LxddTsXJS93e0KgJmBCCmZhGgAxwm6xrTBdwkyGZE7BufAgn1VQTAT8UGEokPKA3OAzRhqk78elbQ5JNOYBSYLcRYx697rDIAxnRSHXgQIwCeVyQ/InKhBCH2KLED5rmmGEKvnXUK8UapxtUe/1BACPIN6bPbzs6cMw+4jpj03VT1yhbXqmz357XZHZsiKYCkUghjCvSBLPf84oHcjGOk5dN/QpaLJEnQRih5kpVhGtxjU+pl3GvrpI+8F+bwNHazNyZBeAguv7teVjfq5EAObyjveCGdgze/qwUihSVwEvmZP//8ppwNfdxFtFnQDMUW3QTJjxWfapbOzqjl3A3RzCeRJ8B3adxHMx612+UDoq/C+sV2znmY+E+ZILb4elWY5i33Ftz8JVyf1m/UGCI5FdNBloZ7DsRuQtPL3TLNWck8jTuLLfdSHHq7kwaX6QvxK54OZBVsk6wKpO+1qnnGNzXOcIDOoQkcM= 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Accelerator Screensharing js CHANGELOG 2 | All notable changes to this project will be documented in this file. 3 | 4 | -------------------------------------- 5 | 6 | #### [1.0.34] 7 | 8 | [ADDED] Optional publisherOptions to start function 9 | 10 | #### [1.0.33] 11 | 12 | [FIXED] Fix OT.checkScreenSharingCapability 13 | 14 | #### [1.0.32] 15 | 16 | [UPDATED] screenDialogsExtensions option 17 | 18 | #### [1.0.31] 19 | 20 | [UPDATED] End screen sharing when the media has stopped. 21 | 22 | #### [1.0.29] 23 | 24 | [UPDATED] Remove screen sharing extension requirement for FireFox 52 and above. 25 | 26 | #### [1.0.28] 27 | 28 | [UPDATED] Compatibility with OpenTok.js 2.13. 29 | 30 | #### [1.0.25] 31 | 32 | [UPDATED] Internal client logging. 33 | 34 | #### [1.0.24] 35 | 36 | [ADDED] AppendControl option 37 | 38 | #### [1.0.23] 39 | 40 | [ADDED] Flow to accept a function as screenSharingContainer. 41 | 42 | #### [1.0.0] 43 | 44 | Official release 45 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | devrel@vonage.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | For anyone looking to get involved to this project, we are glad to hear from you. Here are a few types of contributions 4 | that we would be interested in hearing about. 5 | 6 | - Bug fixes 7 | - If you find a bug, please first report it using Github Issues. 8 | - Issues that have already been identified as a bug will be labelled `bug`. 9 | - If you'd like to submit a fix for a bug, send a Pull Request from your own fork and mention the Issue number. 10 | - Include a test that isolates the bug and verifies that it was fixed. 11 | - New Features 12 | - If you'd like to accomplish something in the library that it doesn't already do, describe the problem in a new Github Issue. 13 | - Issues that have been identified as a feature request will be labelled `enhancement`. 14 | - If you'd like to implement the new feature, please wait for feedback from the project maintainers before spending too much time writing the code. In some cases, `enhancement`s may not align well with the project objectives at the time. 15 | - Tests, Documentation, Miscellaneous 16 | - If you think the test coverage could be improved, the documentation could be clearer, you've got an alternative implementation of something that may have more advantages, or any other change we would still be glad hear about it. 17 | - If its a trivial change, go ahead and send a Pull Request with the changes you have in mind 18 | - If not, open a Github Issue to discuss the idea first. 19 | 20 | ## Requirements 21 | 22 | For a contribution to be accepted: 23 | 24 | - The test suite must be complete and pass 25 | - Code must follow existing styling conventions 26 | - Commit messages must be descriptive. Related issues should be mentioned by number. 27 | 28 | If the contribution doesn't meet these criteria, a maintainer will discuss it with you on the Issue. You can still continue to add more commits to the branch you have sent the Pull Request from. 29 | 30 | ## How To 31 | 32 | 1. Fork this repository on GitHub. 33 | 1. Clone/fetch your fork to your local development machine. 34 | 1. Create a new branch (e.g. `issue-12`, `feat.add_foo`, etc) and check it out. 35 | 1. Make your changes and commit them. (Did the tests pass?) 36 | 1. Push your new branch to your fork. (e.g. `git push myname issue-12`) 37 | 1. Open a Pull Request from your new branch to the original fork's `master` branch. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 OpenTok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Accelerator Screensharing for Javascript 2 | 3 | [![Build Status](https://app.travis-ci.com/opentok/accelerator-screen-sharing-js.svg?branch=main)](https://app.travis-ci.com/github/opentok/accelerator-screen-sharing-js) 4 | [![GitHub release](https://img.shields.io/github/release/opentok/accelerator-screen-sharing-js.svg)](./README.md) 5 | [![license MIT](https://img.shields.io/github/license/mashape/apistatus.svg)](./.github/LICENSE) 6 | [![npm](https://img.shields.io/npm/v/opentok-screen-sharing.svg)](https://www.npmjs.com/package/opentok-screen-sharing) 7 | 8 | Tokbox is now known as Vonage 9 | 10 | ## Quick start 11 | 12 | The OpenTok Screensharing Accelerator Pack provides functionality you can add to your OpenTok applications that enables users to share the screen. This section shows you how to use the accelerator pack. 13 | 14 | ## Install 15 | 16 | ```bash 17 | $ npm install --save opentok-screen-sharing 18 | ``` 19 | 20 | If using browserify or webpack: 21 | 22 | ```javascript 23 | const screenSharing = require('opentok-screen-sharing'); 24 | ``` 25 | 26 | Otherwise, include the accelerator pack in your html: 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | . . . and it will be available in global scope as `ScreenSharingAccPack` 33 | 34 | ----------------- 35 | 36 | Click [here](https://www.npmjs.com/search?q=opentok-acc-pack) for a list of all OpenTok accelerator packs. 37 | 38 | ## Exploring the code 39 | 40 | The `ScreenShareAccPack` class in opentok-screen-sharing.js is the backbone of the screen share feature for the app. 41 | This class sets up the screen share UI views and events, and provides functions for sending, receiving, and rendering shared screens. 42 | 43 | ### Initialization 44 | 45 | The following `options` fields are used in the `ScreenShareAccPack` constructor:
46 | 47 | | Feature | Field | Required | 48 | | ------------------------------------------------------------------------------------- | ------------------------ | -------- | 49 | | Set the OpenTok session (object). | `session` | `true` | 50 | | Set the screen container (string). | `screenSharingContainer` | `false` | 51 | | Set the Common layer API (object). | `accPack` | `false` | 52 | | Set the ID of the Chrome extension (string). | `extensionID` | `false` | 53 | | Append a link tag for Chrome Web Store inline install (boolean) (defaults to `true`). | `appendWebStoreLink` | `false` | 54 | | Set the download path for the FireFox extension (string). | `extentionPathFF` | `false` | 55 | | Using screen sharing with the annotation accelerator pack. | `annotation` | `false` | 56 | | If using annotation, should we use an external window. | `externalWindow` | `false` | 57 | | Set the container to append the start/stop button (string). | `controlsContainer` | `false` | 58 | | Append the start/stop button to the DOM | `appendControl` | `false` | 59 | | Set custom properties for the publisher (object) | `localScreenProperties` | `false` | 60 | | Allow screen sharing over `http` in development (boolean) | `dev` | `false` | 61 | 62 | Once you define the options, you simply create a new instance of the `ScreenShareAccPack`: 63 | 64 | ```javascript 65 | const screenShareOptions = { 66 | session: session, 67 | extensionID: myChromeExtensionID, 68 | extentionPathFF: myFirefoxExentionPath, 69 | screensharingParent: myParentContainer, 70 | }; 71 | const screenSharing = new ScreenShareAccPack(screenShareOptions); 72 | ``` 73 | 74 | #### ScreenShareAccPack Methods 75 | 76 | The `ScreenShareAccPack` component has the following methods: 77 | 78 | | Method | Description | 79 | | ---------------------- | --------------------------------------- | 80 | | `extensionAvailable()` | Test whether an extension is available. | 81 | | `start()` | Start sharing screen. | 82 | | `end()` | Stop sharing screen. | 83 | 84 | #### Events 85 | 86 | The `ScreenSharingAccPack` component emits the following events: 87 | 88 | | Event | Description | 89 | | --------------------- | -------------------------------------------- | 90 | | `startScreenSharing ` | We've started publishing/sharing the screen. | 91 | | `endScreenSharing ` | We've stopped publishing/sharing the screen. | 92 | | `screenSharingError ` | A screen sharing error occurred. | 93 | 94 | If using the screen sharing accelerator pack with [Accelerator Core](https://github.com/opentok/accelerator-core-js), you can subscribe to these events by calling `on` on `otCore` and providing a callback function: 95 | 96 | ```javascript 97 | otCore.on('eventName', callback) 98 | ``` 99 | 100 | ### Multiparty video communication sample app using the Accelerator Screensharing with best-practices for Javascript (https://github.com/opentok/accelerator-sample-apps-js) 101 | 102 | ## Development and Contributing 103 | 104 | Interested in contributing? We :heart: pull requests! See the [Contribution](CONTRIBUTING.md) guidelines. 105 | 106 | ## Getting Help 107 | 108 | We love to hear from you so if you have questions, comments or find a bug in the project, let us know! You can either: 109 | 110 | - Open an issue on this repository 111 | - See for support options 112 | - Tweet at us! We're [@VonageDev](https://twitter.com/VonageDev) on Twitter 113 | - Or [join the Vonage Developer Community Slack](https://developer.nexmo.com/community/slack) 114 | 115 | ## Further Reading 116 | 117 | - Check out the Developer Documentation at 118 | -------------------------------------------------------------------------------- /css/screen-share.css: -------------------------------------------------------------------------------- 1 | /* Screensharing styles */ 2 | 3 | .video-control.share-screen { 4 | margin-left: 10px; 5 | background-image: url("../images/icon/screen-share.png"); 6 | } 7 | 8 | .video-control.share-screen.disabled, 9 | .video-control.share-screen:hover { 10 | background-image: url("../images/icon/screen-share.png"); 11 | } 12 | .video-control.share-screen.active { 13 | background-color: red; 14 | background-image: url("../images/icon/screen-share.png"); 15 | } 16 | 17 | .wms-modal { 18 | position: absolute; 19 | top: 0; 20 | bottom: 0; 21 | left: 0; 22 | right: 0; 23 | background: rgba(0, 0, 0, 0.1); 24 | } 25 | .wms-modal .wms-modal-body { 26 | position: relative; 27 | width: 456px; 28 | height: 286px; 29 | margin: 100px auto 0; 30 | padding: 62px 70px; 31 | background: #ffffff; 32 | border: 1px solid #c7c7c7; 33 | -webkit-border-radius: 8px; 34 | -moz-border-radius: 8px; 35 | border-radius: 8px; 36 | -webkit-box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.35); 37 | -moz-box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.35); 38 | box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.35); 39 | color: #282828; 40 | } 41 | .wms-modal .wms-modal-body .wms-modal-title span { 42 | font-size: 20px; 43 | line-height: 20px; 44 | font-weight: bold; 45 | } 46 | .wms-modal .wms-modal-body .wms-modal-title.with-icon { 47 | position: relative; 48 | padding-left: 70px; 49 | } 50 | .wms-modal .wms-modal-body .wms-modal-title.with-icon i { 51 | position: absolute; 52 | top: 50%; 53 | left: 0; 54 | width: 60px; 55 | height: 41px; 56 | margin-top: -23px; 57 | max-width: 60px; 58 | max-height: 42px; 59 | } 60 | .wms-modal .wms-modal-body p { 61 | font-size: 14px; 62 | margin: 20px 0; 63 | } 64 | .wms-modal .wms-modal-body .wms-btn-install { 65 | display: block; 66 | width: 316px; 67 | height: 44px; 68 | margin: 0 auto; 69 | text-align: center; 70 | line-height: 44px; 71 | color: #fff; 72 | font-size: 14px; 73 | background: #259de8; 74 | -webkit-border-radius: 3px; 75 | -moz-border-radius: 3px; 76 | border-radius: 3px; 77 | } 78 | 79 | .wms-modal .wms-modal-body .wms-cancel-btn-install { 80 | position: absolute; 81 | top: 15px; 82 | right: 15px; 83 | width: 25px; 84 | height: 25px; 85 | background-image: url("../images/icon/oval-281-x.png"); 86 | background-repeat: no-repeat; 87 | background-position: center center; 88 | background-color: transparent; 89 | } 90 | 91 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min--moz-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) { 92 | .wms-modal .wms-modal-body .wms-cancel-btn-install { 93 | background-image: url("../images/icon/oval-281-x@2x.png"); 94 | background-size: 25px 25px; 95 | } 96 | } 97 | 98 | .wms-icon-share-large { 99 | background-image: url("../images/icon/screenshare-large.png"); 100 | background-repeat: no-repeat; 101 | background-position: center center; 102 | } 103 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min--moz-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) { 104 | .wms-icon-share-large { 105 | background-image: url("../images/icon/screenshare-large@2x.png"); 106 | background-size: 60px 41px; 107 | } 108 | } -------------------------------------------------------------------------------- /images/icon/screen-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentok/accelerator-screen-sharing-js/6794d34051c9ce3bab38ec802b648205bdffc4d5/images/icon/screen-share.png -------------------------------------------------------------------------------- /images/icon/screen-share@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentok/accelerator-screen-sharing-js/6794d34051c9ce3bab38ec802b648205bdffc4d5/images/icon/screen-share@2x.png -------------------------------------------------------------------------------- /images/icon/screenshare-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentok/accelerator-screen-sharing-js/6794d34051c9ce3bab38ec802b648205bdffc4d5/images/icon/screenshare-large.png -------------------------------------------------------------------------------- /images/icon/screenshare-large@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentok/accelerator-screen-sharing-js/6794d34051c9ce3bab38ec802b648205bdffc4d5/images/icon/screenshare-large@2x.png -------------------------------------------------------------------------------- /images/main/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentok/accelerator-screen-sharing-js/6794d34051c9ce3bab38ec802b648205bdffc4d5/images/main/mask.png -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | var customLaunchers = { 3 | Chrome_travis_ci: { 4 | base: 'Chrome', 5 | flags: ['--no-sandbox'] 6 | } 7 | }; 8 | 9 | var configuration = { 10 | plugins: [ 11 | 'karma-mocha', 'karma-coverage', 'karma-html2js-preprocessor', 'karma-chrome-launcher', 'karma-chai' 12 | ], 13 | basePath: '', 14 | frameworks: ['mocha', 'chai'], 15 | client: { 16 | contextFile: '/test/index.html' 17 | }, 18 | files: [ 19 | 'https://static.opentok.com/v2/js/opentok.min.js', 20 | 'https://code.jquery.com/jquery-1.10.2.js', 21 | 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js', 22 | 'node_modules/opentok-solutions-logging/src/opentok-solutions-logging.js', 23 | 'dist/opentok-screen-sharing.js', 24 | 'test/components/accelerator-pack.js', 25 | 'test/opentok-screen-sharing-test.js' 26 | ], 27 | exclude: [ 28 | ], 29 | preprocessors: { 30 | 'test/*.html': ['html2js'], 31 | 'src/*.js': ['coverage'] 32 | }, 33 | reporters: ['progress', 'coverage', 'dots'], 34 | port: 9877, 35 | colors: true, 36 | autoWatch: true, 37 | browsers: [], 38 | singleRun: true, 39 | logLevel: config.LOG_INFO, 40 | coverageReporter: { 41 | dir: 'test/coverage', 42 | instrumenter: { 43 | 'src/*.js': ['istanbul'] 44 | }, 45 | reporters: [ 46 | { type: 'html', subdir: 'report-html' }, 47 | { type: 'lcov', subdir: 'report-lcov' }, 48 | { type: 'lcovonly', subdir: '.', file: 'report-lcovonly.txt' } 49 | ] 50 | }, 51 | }; 52 | 53 | if (process.env.TRAVIS) { 54 | configuration.customLaunchers = customLaunchers; 55 | configuration.browsers = Object.keys(customLaunchers); 56 | } 57 | config.set(configuration); 58 | }; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentok-screen-sharing", 3 | "version": "1.0.35", 4 | "description": "OpenTok screen sharing accelerator pack", 5 | "main": "dist/opentok-screen-sharing.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "build:css": "copyfiles css/*.css dist/", 11 | "build:js": "concat -o dist/opentok-screen-sharing.js src/detect-browser.js src/opentok-screen-sharing.js & uglifyjs dist/opentok-screen-sharing.js -o dist/opentok-screen-sharing.min.js", 12 | "build:images": "copyfiles images/**/* dist/", 13 | "build:logversion": "replace '(js-vsol-)(x.y.z)' '$1'$npm_package_version ./dist", 14 | "build": "mkdir dist && npm run build:js & npm run build:css & npm run build:images && npm run build:logversion", 15 | "test": "karma start" 16 | }, 17 | "babel": { 18 | "stage": "1" 19 | }, 20 | "keywords": [ 21 | "opentok", 22 | "annotation", 23 | "opentok-acc-pack", 24 | "screen-sharing", 25 | "audio/video" 26 | ], 27 | "author": "adrice727@gmail.com", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/opentok/accelerator-screen-sharing-js" 32 | }, 33 | "devDependencies": { 34 | "babel-plugin-espower": "^3.0.1", 35 | "chai": "^4.2.0", 36 | "concat": "^1.0.3", 37 | "copyfiles": "^2.3.0", 38 | "coverage": "^0.4.1", 39 | "html2js": "^0.2.0", 40 | "karma": "^5.1.1", 41 | "karma-chai": "^0.1.0", 42 | "karma-chrome-launcher": "^3.1.0", 43 | "karma-coverage": "^2.0.3", 44 | "karma-html2js-preprocessor": "^1.1.0", 45 | "karma-mocha": "^2.0.1", 46 | "karma-requirejs": "^1.1.0", 47 | "karma-sinon": "^1.0.5", 48 | "merge-stream": "^2.0.0", 49 | "mocha": "^8.1.3", 50 | "npm-css": "^0.2.3", 51 | "uglify-js": "^3.10.2" 52 | }, 53 | "dependencies": { 54 | "jquery": "^3.5.1", 55 | "opentok-solutions-logging": "^1.1.0", 56 | "replace": "^1.2.1", 57 | "underscore": "^1.11.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/detect-browser.js: -------------------------------------------------------------------------------- 1 | function detectBrowser() { 2 | 3 | var navigator = window && window.navigator; 4 | 5 | // Fail early if it's not a browser 6 | if (typeof window === 'undefined' || !window.navigator) { 7 | return 'not a browser'; 8 | } 9 | 10 | // Firefox. 11 | if (navigator.mozGetUserMedia) { 12 | return 'Firefox'; 13 | } 14 | if (navigator.webkitGetUserMedia) { 15 | // Chrome, Chromium, Webview, Opera, all use the chrome shim for now 16 | if (window.hasOwnProperty('webkitRTCPeerConnection')) { 17 | return 'Chrome'; 18 | } 19 | if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { 20 | return 'Safari'; 21 | } 22 | return 'WebKit browser without WebRTC support'; 23 | } 24 | 25 | if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. 26 | return 'Edge'; 27 | } 28 | 29 | if (navigator.userAgent.indexOf('MSIE ') > 0 || 30 | !!navigator.userAgent.match(/Trident.*rv\:11\./)) { 31 | return 'Internet Explorer'; 32 | } 33 | 34 | if (navigator.mediaDevices && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { 35 | // Safari, with webkitGetUserMedia removed. 36 | return 'Safari'; 37 | } 38 | // Default fallthrough: not supported. 39 | return 'unsupported browser'; 40 | }; 41 | 42 | function firefoxExtensionRequired() { 43 | var match = navigator.userAgent.match(/(?:firefox|iceweasel|fxios)[ \/](\d+(\.\d+)?)/i); 44 | var version = (match && match.length > 1 && match[1]) || ''; 45 | return version < '52.0'; 46 | }; 47 | -------------------------------------------------------------------------------- /src/opentok-screen-sharing.js: -------------------------------------------------------------------------------- 1 | /* global chrome OT ScreenSharingAccPack OTKAnalytics define */ 2 | (function () { 3 | 4 | /** Include external dependencies */ 5 | var _; 6 | var $; 7 | var OTKAnalytics; 8 | 9 | if (typeof module === 'object' && typeof module.exports === 'object') { 10 | /* eslint-disable import/no-unresolved */ 11 | _ = require('underscore'); 12 | $ = require('jquery'); 13 | OTKAnalytics = require('opentok-solutions-logging'); 14 | /* eslint-enable import/no-unresolved */ 15 | } else { 16 | _ = this._; 17 | $ = this.$; 18 | OTKAnalytics = this.OTKAnalytics; 19 | } 20 | 21 | /** Private Variables*/ 22 | var _this; // Reference to instance of ScreenSharingAccPack 23 | var _active; // Currently sharing screen? 24 | var _accPack; // Common layer API 25 | var _session; // OpenTok session 26 | 27 | var _screenSharingControl = [ 28 | '' 29 | ].join('\n'); 30 | 31 | var _screenSharingView = [ 32 | '' 42 | ].join('\n'); 43 | 44 | var _screenDialogsExtensions = [ 45 | /* eslint-disable max-len */ 46 | '', 57 | '' 68 | /* eslint-enable max-len */ 69 | ].join('\n'); 70 | 71 | /** Private Methods */ 72 | 73 | /** Analytics */ 74 | var _otkanalytics; 75 | 76 | // vars for the analytics logs. Internal use 77 | var _logEventData = { 78 | clientVersion: 'js-vsol-x.y.z', // x.y.z filled by npm build script 79 | componentId: 'screenSharingAccPack', 80 | name: 'guidScreensharingAccPack', 81 | actionInitialize: 'Init', 82 | actionStart: 'Start', 83 | actionEnd: 'End', 84 | enableAnnotations: 'EnableAnnotations', 85 | disableAnnotations: 'DisableAnnotations', 86 | enableAudioScreenSharing: 'EnableAudioScreenSharing', 87 | disableAudioScreenSharing: 'DisableAudioScreenSharing', 88 | variationAttempt: 'Attempt', 89 | variationError: 'Failure', 90 | variationSuccess: 'Success', 91 | }; 92 | 93 | var _logAnalytics = function () { 94 | 95 | // init the analytics logs 96 | var _source = window.location.href; 97 | 98 | var otkanalyticsData = { 99 | clientVersion: _logEventData.clientVersion, 100 | source: _source, 101 | componentId: _logEventData.componentId, 102 | name: _logEventData.name 103 | }; 104 | 105 | _otkanalytics = new OTKAnalytics(otkanalyticsData); 106 | 107 | var sessionInfo = { 108 | sessionId: _session.id, 109 | connectionId: _session.connection.connectionId, 110 | partnerId: _session.apiKey 111 | }; 112 | 113 | _otkanalytics.addSessionInfo(sessionInfo); 114 | 115 | }; 116 | 117 | var _log = function (action, variation) { 118 | 119 | var data = { 120 | action: action, 121 | variation: variation 122 | }; 123 | _otkanalytics.logEvent(data); 124 | }; 125 | 126 | var _defaultScreenProperties = { 127 | insertMode: 'append', 128 | width: '100%', 129 | height: '100%', 130 | showControls: false, 131 | style: { 132 | buttonDisplayMode: 'off', 133 | }, 134 | videoSource: 'window', 135 | }; 136 | 137 | var _setupUI = function (parent) { 138 | $('body').append(_this.screenDialogsExtensions); 139 | _this.appendControl && $(_this.controlsContainer).append(_screenSharingControl); 140 | $(parent).append(_screenSharingView); 141 | }; 142 | 143 | var _toggleScreenSharingButton = function (show) { 144 | $('#startScreenSharing')[show ? 'show' : 'hide'](); 145 | }; 146 | 147 | // Trigger event via common layer API 148 | var _triggerEvent = function (event, data) { 149 | if (_accPack) { 150 | _accPack.triggerEvent(event, data); 151 | } 152 | }; 153 | 154 | /** 155 | * Create a publisher for the screen. If we're using annotation, we first need 156 | * to create the annotion window and get a reference to its annotation container 157 | * element so that we can pass it to the initPublisher function. 158 | * @returns {promise} < Resolve: [Object] Container element for annotation in external window > 159 | */ 160 | var _initPublisher = function (publisherOptions) { 161 | 162 | var createPublisher = function (publisherDiv) { 163 | 164 | var innerDeferred = $.Deferred(); 165 | 166 | var getContainer = function () { 167 | if (publisherDiv) { return publisherDiv; } 168 | if (typeof _this.screenSharingContainer === 'function') { 169 | return document.querySelector(_this.screenSharingContainer('publisher', 'screen')); 170 | } else { 171 | return _this.screenSharingContainer; 172 | } 173 | } 174 | 175 | var container = getContainer(); 176 | 177 | var properties = Object.assign({}, _this.localScreenProperties || _defaultScreenProperties, publisherOptions); 178 | 179 | _this.publisher = OT.initPublisher(container, properties, function (error) { 180 | if (error) { 181 | _triggerEvent('screenSharingError', error); 182 | innerDeferred.reject(_.extend(_.omit(error, 'messsage'), { 183 | message: 'Error starting the screen sharing', 184 | })); 185 | } else { 186 | _this.publisher.on('mediaStopped', function () { 187 | end(); 188 | }); 189 | innerDeferred.resolve(); 190 | } 191 | }); 192 | 193 | return innerDeferred.promise(); 194 | }; 195 | 196 | var outerDeferred = $.Deferred(); 197 | 198 | if (_this.annotation && _this.externalWindow) { 199 | _log(_logEventData.enableAnnotations, _logEventData.variationSuccess); 200 | 201 | _accPack.setupExternalAnnotation() 202 | .then(function (annotationWindow) { 203 | _this.annotationWindow = annotationWindow || null; 204 | var annotationElements = annotationWindow.createContainerElements(); 205 | createPublisher(annotationElements.publisher) 206 | .then(function () { 207 | outerDeferred.resolve(annotationElements.annotation); 208 | }); 209 | 210 | }); 211 | } else { 212 | 213 | createPublisher() 214 | .then(function () { 215 | outerDeferred.resolve(); 216 | }); 217 | 218 | } 219 | 220 | return outerDeferred.promise(); 221 | }; 222 | 223 | 224 | /** 225 | * Start publishing the screen 226 | * @param annotationContainer 227 | */ 228 | var _publish = function (annotationContainer) { 229 | 230 | _session.publish(_this.publisher, function (error) { 231 | if (error) { 232 | 233 | // Let's write our own error message 234 | var customError = _.omit(error, 'message'); 235 | 236 | if (error.code === 1500 && navigator.userAgent.indexOf('Firefox') !== -1) { 237 | $('#dialog-form-ff').toggle(); 238 | } else { 239 | 240 | var errorMessage; 241 | 242 | if (error.code === 1010) { 243 | errorMessage = 'Check your network connection'; 244 | } else { 245 | errorMessage = 'Error sharing the screen'; 246 | } 247 | 248 | customError.message = errorMessage; 249 | _triggerEvent('screenSharingError', customError); 250 | _log(_logEventData.actionStart, _logEventData.variationError); 251 | } 252 | } else { 253 | if (_this.annotation && _this.externalWindow) { 254 | _accPack.linkAnnotation(_this.publisher, annotationContainer, _this.annotationWindow); 255 | _log(_logEventData.actionInitialize, _logEventData.variationSuccess); 256 | } 257 | _active = true; 258 | _triggerEvent('startScreenSharing', _this.publisher); 259 | _log(_logEventData.actionStart, _logEventData.variationSuccess); 260 | } 261 | }); 262 | 263 | }; 264 | 265 | /** 266 | * Stop publishing the screen 267 | */ 268 | var _stopPublishing = function () { 269 | _session.unpublish(_this.publisher); 270 | }; 271 | 272 | /** Public Methods */ 273 | 274 | var extensionAvailable = function () { 275 | 276 | var deferred = $.Deferred(); 277 | 278 | if (window.location.protocol === 'http:' && !_this.dev) { 279 | alert("Screensharing only works under 'https', please add 'https://' in front of your debugger url."); 280 | deferred.reject('https required'); 281 | } 282 | 283 | OT.checkScreenSharingCapability(function (response) { 284 | if (!response.supported || response.extensionRegistered === false) { 285 | alert('This browser does not support screen sharing! Please use Chrome, Firefox or IE!'); 286 | deferred.reject('browser support not available'); 287 | } else if (response.extensionInstalled === false) { 288 | if (detectBrowser() === 'Firefox') { 289 | if (!firefoxExtensionRequired()) { 290 | deferred.resolve(); 291 | } else { 292 | $('#dialog-form-ff').toggle(); 293 | deferred.reject('screensharing extension not installed'); 294 | } 295 | } else { 296 | $('#dialog-form-chrome').toggle(); 297 | deferred.reject('screensharing extension not installed'); 298 | } 299 | } else { 300 | deferred.resolve(); 301 | } 302 | }); 303 | 304 | return deferred.promise(); 305 | 306 | }; 307 | /** 308 | * @param {Object} [publisherOptions] - Properties for the screen sharing publisher. 309 | */ 310 | var start = function (publisherOptions) { 311 | _log(_logEventData.actionStart, _logEventData.variationAttempt); 312 | extensionAvailable(_this.extensionID, _this.extensionPathFF) 313 | .then(_initPublisher(publisherOptions)) 314 | .then(_publish) 315 | .fail(function (error) { 316 | console.log('Error starting screensharing: ', error); 317 | _log(_logEventData.actionStart, _logEventData.variationError); 318 | }); 319 | }; 320 | 321 | var end = function (callEnded) { 322 | _stopPublishing(); 323 | _active = false; 324 | if (callEnded) { 325 | _toggleScreenSharingButton(false); 326 | } 327 | _triggerEvent('endScreenSharing', _this.publisher); 328 | _log(_logEventData.actionEnd, _logEventData.variationSuccess); 329 | }; 330 | 331 | /** Events */ 332 | 333 | var _registerEvents = function () { 334 | 335 | if (!_accPack) { 336 | return; 337 | } 338 | 339 | var events = ['startScreenSharing', 'endScreenSharing', 'screenSharingError']; 340 | _accPack.registerEvents(events); 341 | }; 342 | 343 | var _addScreenSharingListeners = function () { 344 | 345 | var startOrEnd = _.throttle(function () { 346 | !!_active ? end() : start(); 347 | }, 750); 348 | 349 | $('#startScreenSharing').on('click', startOrEnd); 350 | 351 | /** Handlers for screensharing extension modal */ 352 | $('#btn-install-plugin-chrome').on('click', function () { 353 | window.open(['https://chrome.google.com/webstore/detail/', _this.extensionID].join(''), '_blank'); 354 | $('#dialog-form-chrome').toggle(); 355 | }); 356 | 357 | $('#btn-cancel-plugin-chrome').on('click', function () { 358 | $('#dialog-form-chrome').toggle(); 359 | }); 360 | 361 | $('#btn-install-plugin-ff').prop('href', _this.extensionPathFF); 362 | 363 | $('#btn-install-plugin-ff').on('click', function () { 364 | $('#dialog-form-ff').toggle(); 365 | }); 366 | 367 | $('#btn-cancel-plugin-ff').on('click', function () { 368 | $('#dialog-form-ff').toggle(); 369 | }); 370 | 371 | if (!!_accPack) { 372 | 373 | _accPack.registerEventListener('startCall', function () { 374 | _toggleScreenSharingButton(true); 375 | }); 376 | 377 | _accPack.registerEventListener('endCall', function () { 378 | if (_active) { 379 | end(true); 380 | } else { 381 | _toggleScreenSharingButton(false); 382 | } 383 | }); 384 | 385 | _accPack.registerEventListener('annotationWindowClosed', function () { 386 | end(); 387 | }); 388 | } 389 | 390 | }; 391 | 392 | var _validateExtension = function (extensionID, extensionPathFF, appendWebStoreLink) { 393 | 394 | if (detectBrowser() === 'Chrome') { 395 | if (!extensionID || !extensionID.length) { 396 | throw new Error('Error starting the screensharing. Chrome extensionID required'); 397 | } else { 398 | if (appendWebStoreLink) { 399 | $('', { 400 | rel: 'chrome-webstore-item', 401 | href: ['https://chrome.google.com/webstore/detail/', extensionID].join('') 402 | }).appendTo('head'); 403 | } 404 | OT.registerScreenSharingExtension('chrome', extensionID, 2); 405 | } 406 | } 407 | 408 | }; 409 | 410 | var _validateOptions = function (options) { 411 | 412 | if (!_.property('session', options)) { 413 | throw new Error('Screen Share Acc Pack requires an OpenTok session'); 414 | } 415 | 416 | _session = _.property('session')(options); 417 | _accPack = _.property('accPack')(options); 418 | 419 | var appendLink = options.appendWebStoreLink === undefined ? true : options.appendWebStoreLink; 420 | 421 | _validateExtension(_.property('extensionID')(options), _.property('extensionPathFF')(options), appendLink); 422 | }; 423 | 424 | /** 425 | * @constructor 426 | * Represents a screensharing component 427 | * @param {Object} options 428 | * @param {String} options.session 429 | * @param {Object} [options.accPack] 430 | * @param {String} [options.extensionID] 431 | * @param {String} [options.appendWebStoreLink] 432 | * @param {String} [options.extentionPathFF] 433 | * @param {String} [options.screensharingParent] 434 | * @param {String} [options.screenDialogsExtensions] 435 | * @param {String | Function} [options.screensharingContainer] 436 | */ 437 | var ScreenSharingAccPack = function (options) { 438 | 439 | _this = this; 440 | 441 | // Check for required options 442 | _validateOptions(options); 443 | 444 | // Extend our instance 445 | var optionsProps = [ 446 | 'annotation', 447 | 'externalWindow', 448 | 'extensionURL', 449 | 'extensionID', 450 | 'appendWebStoreLink', 451 | 'extensionPathFF', 452 | 'screenSharingContainer', 453 | 'screenSharingParent', 454 | 'controlsContainer', 455 | 'screenProperties', 456 | 'localScreenProperties', 457 | 'appendControl', 458 | 'dev', 459 | 'screenDialogsExtensions', 460 | ]; 461 | 462 | 463 | _.extend(_this, _.defaults(_.pick(options, optionsProps), { 464 | screenSharingParent: '#videoContainer', 465 | screenSharingContainer: document.getElementById('videoHolderSharedScreen'), 466 | controlsContainer: '#feedControls', 467 | appendWebStoreLink: true, 468 | appendControl: true, 469 | screenDialogsExtensions: _screenDialogsExtensions, 470 | })); 471 | 472 | 473 | 474 | // Do UIy things 475 | _setupUI(_this.screensharingParent); 476 | _registerEvents(); 477 | _addScreenSharingListeners(); 478 | 479 | // init analytics logs 480 | _logAnalytics(); 481 | _log(_logEventData.actionInitialize, _logEventData.variationSuccess); 482 | }; 483 | 484 | ScreenSharingAccPack.prototype = { 485 | constructor: ScreenSharingAccPack, 486 | extensionAvailable: extensionAvailable, 487 | start: start, 488 | end: end 489 | }; 490 | 491 | if (typeof exports === 'object') { 492 | module.exports = ScreenSharingAccPack; 493 | } else if (typeof define === 'function' && define.amd) { 494 | define(function () { 495 | return ScreenSharingAccPack; 496 | }); 497 | } else { 498 | this.ScreenSharingAccPack = ScreenSharingAccPack; 499 | } 500 | 501 | }.call(this)); 502 | -------------------------------------------------------------------------------- /test/components/accelerator-pack.js: -------------------------------------------------------------------------------- 1 | /* global OT AnnotationAccPack ScreenSharingAccPack define */ 2 | 3 | (function () { 4 | 5 | var _this; 6 | var _session; 7 | var _annotation; 8 | var _screensharing; // eslint-disable-line no-unused-vars 9 | var _streams = {}; 10 | 11 | var _commonOptions = { 12 | localCallProperties: { 13 | insertMode: 'append', 14 | width: '100%', 15 | height: '100%', 16 | showControls: false, 17 | style: { 18 | buttonDisplayMode: 'off' 19 | }, 20 | }, 21 | localScreenProperties: { 22 | insertMode: 'append', 23 | width: '100%', 24 | height: '100%', 25 | videoSource: 'window', 26 | showControls: false, 27 | style: { 28 | buttonDisplayMode: 'off' 29 | }, 30 | name: 'web' 31 | } 32 | }; 33 | 34 | /** 35 | * Private methods 36 | */ 37 | 38 | /** Eventing */ 39 | var _events = {}; // {eventName: [callbacks functions . . .]} 40 | var _isRegisteredEvent = _.partial(_.has, _events); 41 | 42 | /** 43 | * Register events that can be listened to be other components/modules 44 | * @param {array | string} events - A list of event names. A single event may 45 | * also be passed as a string. 46 | * @returns {function} See triggerEvent 47 | */ 48 | var registerEvents = function (events) { 49 | 50 | var eventList = Array.isArray(events) ? events : [events]; 51 | 52 | _.each(eventList, function (event) { 53 | if (!_isRegisteredEvent(event)) { 54 | _events[event] = []; 55 | } 56 | }); 57 | 58 | }; 59 | 60 | /** 61 | * Register an event listener with the AP layer 62 | * @param {string} event - The name of the event 63 | * @param {function} callback - The function invoked upon the event 64 | */ 65 | var registerEventListener = function (event, callback) { 66 | 67 | if (typeof callback !== 'function') { 68 | throw new Error('Provided callback is not a function'); 69 | } 70 | 71 | if (!_isRegisteredEvent(event)) { 72 | registerEvents(event); 73 | } 74 | 75 | _events[event].push(callback); 76 | }; 77 | 78 | /** 79 | * Stop a callback from being fired when an event is triggered 80 | * @param {string} event - The name of the event 81 | * @param {function} callback - The function invoked upon the event 82 | */ 83 | var removeEventListener = function (event, callback) { 84 | 85 | if (typeof callback !== 'function') { 86 | throw new Error('Provided callback is not a function'); 87 | } 88 | 89 | var listeners = _events[event]; 90 | 91 | if (!listeners || !listeners.length) { 92 | return; 93 | } 94 | 95 | var index = listeners.indexOf(callback); 96 | 97 | if (index !== -1) { 98 | listeners.splice(index, 1); 99 | } 100 | 101 | }; 102 | 103 | /** 104 | * Fire all registered callbacks for a given event 105 | * @param {string} event - The event name 106 | * @param {*} data - Data to be passed to the callback functions 107 | */ 108 | var triggerEvent = function (event, data) { 109 | if (_.has(_events, event)) { 110 | _.each(_events[event], function (fn) { 111 | fn(data); 112 | }); 113 | } 114 | }; 115 | 116 | /** 117 | * @param [string] type - A subset of common options 118 | */ 119 | var getOptions = function (type) { 120 | 121 | return type ? _commonOptions[type] : _commonOptions; 122 | 123 | }; 124 | 125 | var _validateOptions = function (options) { 126 | 127 | var requiredProps = ['sessionId', 'apiKey', 'token']; 128 | 129 | _.each(requiredProps, function (prop) { 130 | if (!_.property(prop)(options)) { 131 | throw new Error('Accelerator Pack requires a session ID, apiKey, and token'); 132 | } 133 | }); 134 | 135 | return options; 136 | }; 137 | 138 | /** 139 | * Returns the current session 140 | */ 141 | var getSession = function () { 142 | return _session; 143 | }; 144 | 145 | /** 146 | * Initialize the annotation component for use in external window 147 | * @returns {Promise} < Resolve: [Object] External annotation window > 148 | */ 149 | var setupExternalAnnotation = function () { 150 | return _annotation.start(_session, { 151 | screensharing: true 152 | }); 153 | }; 154 | 155 | /** 156 | * Initialize the annotation component for use in external window 157 | * @returns {Promise} < Resolve: [Object] External annotation window > 158 | */ 159 | var endExternalAnnotation = function () { 160 | return _annotation.end(true); 161 | }; 162 | 163 | /** 164 | * Initialize the annotation component for use in current window 165 | * @returns {Promise} < Resolve: [Object] External annotation window > 166 | */ 167 | var setupAnnotationView = function (subscriber) { 168 | var canvasContainer = document.getElementById('videoHolderSharedScreen'); 169 | var videoContainer = document.getElementById('videoContainer'); 170 | var annotationOptions = { 171 | canvasContainer: canvasContainer 172 | }; 173 | _annotation.start(_session, annotationOptions) 174 | .then(function () { 175 | var mainContainer = document.getElementById('otsWidget'); 176 | mainContainer.classList.add('aspect-ratio'); 177 | _annotation.linkCanvas(subscriber, canvasContainer, { 178 | absoluteParent: videoContainer 179 | }); 180 | _annotation.resizeCanvas(); 181 | }); 182 | }; 183 | 184 | /** 185 | * Initialize the annotation component for use in current window 186 | * @returns {Promise} < Resolve: [Object] External annotation window > 187 | */ 188 | var endAnnotationView = function () { 189 | _annotation.end(); 190 | var mainContainer = document.getElementById('otsWidget'); 191 | mainContainer.classList.remove('aspect-ratio'); 192 | }; 193 | 194 | /** 195 | * Connect the annotation canvas to the publisher or subscriber 196 | * @param {Object} pubSub - The publisher or subscriber 197 | * @param {Object} annotationContainer 198 | * @param [Object] externalWindow 199 | * 200 | */ 201 | var linkAnnotation = function (pubSub, annotationContainer, externalWindow) { 202 | _annotation.linkCanvas(pubSub, annotationContainer, { 203 | externalWindow: externalWindow 204 | }); 205 | 206 | if (externalWindow) { 207 | var subscriberStream = _.findWhere(_streams, { videoType: 'camera' }); 208 | if (!!subscriberStream) { 209 | _annotation.addSubscriberToExternalWindow(subscriberStream); 210 | } 211 | } 212 | }; 213 | 214 | var _registerSessionEvents = function () { 215 | 216 | registerEvents(['streamCreated', 'streamDestroyed', 'sessionError']); 217 | 218 | _session.on({ 219 | streamCreated: function (event) { 220 | _streams[event.stream.id] = event.stream; 221 | triggerEvent('streamCreated', event); 222 | }, 223 | streamDestroyed: function (event) { 224 | delete _streams[event.stream.id]; 225 | triggerEvent('streamDestroyed', event); 226 | } 227 | }); 228 | }; 229 | 230 | 231 | var _setupEventListeners = function () { 232 | registerEventListener('startViewingSharedScreen', setupAnnotationView); 233 | registerEventListener('endViewingSharedScreen', endAnnotationView); 234 | registerEventListener('endScreenSharing', endExternalAnnotation); 235 | }; 236 | 237 | /** 238 | * Initialize any of the accelerator pack components included in the application. 239 | */ 240 | var _initAccPackComponents = _.once(function () { 241 | 242 | if (!!ScreenSharingAccPack) { 243 | 244 | var screensharingProps = [ 245 | 'sessionId', 246 | 'annotation', 247 | 'extensionURL', 248 | 'extensionID', 249 | 'extensionPathFF', 250 | 'screensharingContainer' 251 | ]; 252 | 253 | var screensharingOptions = _.extend(_.pick(_this.options, screensharingProps), 254 | _this.options.screensharing, { 255 | session: _session, 256 | accPack: _this, 257 | localScreenProperties: _commonOptions.localScreenProperties 258 | }); 259 | 260 | _screensharing = new ScreenSharingAccPack(screensharingOptions); 261 | } 262 | 263 | if (!!AnnotationAccPack) { 264 | 265 | _annotation = new AnnotationAccPack(_.extend({}, _this.options, { 266 | session: _session, 267 | accPack: _this 268 | })); 269 | } 270 | 271 | _setupEventListeners(); 272 | 273 | }); 274 | 275 | /** 276 | * @constructor 277 | * Provides a common layer for logic and API for accelerator pack components 278 | */ 279 | var AcceleratorPack = function (options) { 280 | 281 | _this = this; 282 | _this.options = _validateOptions(options); 283 | 284 | _session = OT.initSession(options.apiKey, options.sessionId); 285 | _registerSessionEvents(); 286 | 287 | // Connect 288 | _session.connect(options.token, function (error) { 289 | if (error) { 290 | triggerEvent('sessionError', error); 291 | } 292 | 293 | }); 294 | 295 | registerEventListener('startCall', _initAccPackComponents); 296 | 297 | }; 298 | 299 | AcceleratorPack.prototype = { 300 | constructor: AcceleratorPack, 301 | registerEvents: registerEvents, 302 | triggerEvent: triggerEvent, 303 | registerEventListener: registerEventListener, 304 | removeEventListener: removeEventListener, 305 | getSession: getSession, 306 | getOptions: getOptions, 307 | setupAnnotationView: setupAnnotationView, 308 | setupExternalAnnotation: setupExternalAnnotation, 309 | linkAnnotation: linkAnnotation 310 | }; 311 | 312 | if (typeof exports === 'object') { 313 | module.exports = AcceleratorPack; 314 | } else if (typeof define === 'function' && define.amd) { 315 | define(function () { 316 | return AcceleratorPack; 317 | }); 318 | } else { 319 | this.AcceleratorPack = AcceleratorPack; 320 | } 321 | 322 | }.call(this)); 323 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ScreenSharing AccPack Tests 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /test/opentok-screen-sharing-test.js: -------------------------------------------------------------------------------- 1 | 2 | var expect = chai.expect; 3 | var AcceleratorPack = AcceleratorPack; 4 | var ScreenSharingAccPack = ScreenSharingAccPack; 5 | 6 | var _optionsap; 7 | var _accPack; 8 | var _connection; 9 | var _session; 10 | var _screenShare; 11 | var _options; 12 | 13 | if (!Function.prototype.bind) { 14 | Function.prototype.bind = function() { 15 | var fn = this, 16 | args = Array.prototype.slice.call(arguments), 17 | context = args.shift(); 18 | return function() { 19 | fn.apply(context, args); 20 | }; 21 | }; 22 | } 23 | 24 | var _screenSharingConstructor = function(options){ 25 | _screenShare = new ScreenSharingAccPack(options); 26 | }; 27 | 28 | describe('Screen Sharing Acc Pack', function() { 29 | 30 | before(function() { 31 | 32 | _optionsap = { 33 | apiKey: 'yourAPIKey', // Replace with an OpenTok API key 34 | sessionId: 'yourSessionID', // Replace with a generated Session ID 35 | token: 'yourToken' 36 | }; 37 | 38 | _accPack = new AcceleratorPack(_optionsap); 39 | 40 | _connection = { 41 | connectionId: "ConnectionIDTest", 42 | }; 43 | 44 | _session = { 45 | id: 'yourSessionID', // Replace with a generated Session ID 46 | connection: _connection, 47 | apiKey: 'yourAPIKey', // Replace with an OpenTok API key 48 | token: 'yourToken' 49 | }; 50 | 51 | _options = { 52 | session: _session, 53 | extensionID: "ExtensionIDTest", 54 | accPack: _accPack 55 | }; 56 | }); 57 | 58 | after(function(){ 59 | _screenShare = null; 60 | _accPack = null; 61 | }); 62 | 63 | describe('Test New SS instance', function() { 64 | 65 | it('Constructor should create a SSAP instance', function() { 66 | _screenShare = new ScreenSharingAccPack(_options); 67 | expect(_screenShare).not.to.be.null; 68 | expect(_screenShare.publisher).not.to.be.null; 69 | }); 70 | 71 | }); 72 | 73 | describe('Test SS new instance when argument options is not complete', function() { 74 | 75 | before(function() { 76 | _screenShare = null; 77 | }); 78 | 79 | it('Should throw an exception when session is missing', function() { 80 | var options = { 81 | extensionID: "ExtensionIDTest" 82 | }; 83 | //expect(_screenSharingConstructor.bind(_screenSharingConstructor, options)).to.throw('Screen Share Acc Pack requires an OpenTok session'); 84 | expect(_screenSharingConstructor.bind(_screenSharingConstructor, options)).to.throw(''); 85 | expect(_screenShare).to.be.null; 86 | }); 87 | 88 | it('Should throw an exception when extensionID is missing', function() { 89 | var options = { 90 | session: _session, 91 | accPack: _accPack 92 | }; 93 | expect(_screenSharingConstructor.bind(_screenSharingConstructor, options)).to.throw('Error starting the screensharing. Chrome extensionID required'); 94 | expect(_screenShare).to.be.null; 95 | }); 96 | 97 | it('Should not throw an exception when accpack is missing', function() { 98 | var options = { 99 | session: _session, 100 | extensionID: "ExtensionIDTest" 101 | }; 102 | expect(_screenSharingConstructor.bind(_screenSharingConstructor, options)).not.to.throw(''); 103 | expect(_screenShare).not.to.be.null; 104 | expect(_screenShare.publisher).not.to.be.null; 105 | }); 106 | }); 107 | 108 | describe('Test SS new instance when session is not complete', function(){ 109 | 110 | before(function() { 111 | _screenShare = null; 112 | }); 113 | 114 | it('Should throw an exception when sessionId is null', function() { 115 | var session = { 116 | id: "", 117 | connection: "Test", 118 | apiKey: "Test", 119 | token: "Test" 120 | }; 121 | var options = { 122 | session: session, 123 | extensionID: "ExtensionIDTest" 124 | }; 125 | expect(_screenSharingConstructor.bind(_screenSharingConstructor, options)).to.throw('The sessionId field cannot be null in the log entry'); 126 | expect(_screenShare).to.be.null; 127 | }); 128 | 129 | it('Should throw an exception when connectionId is missing', function() { 130 | var session = { 131 | id: "Test", 132 | connection: "Test", 133 | apiKey: "Test", 134 | token: "Test" 135 | }; 136 | var options = { 137 | session: session, 138 | extensionID: "ExtensionIDTest" 139 | }; 140 | expect(_screenSharingConstructor.bind(_screenSharingConstructor, options)).to.throw('The connectionId field cannot be null in the log entry'); 141 | expect(_screenShare).to.be.null; 142 | }); 143 | 144 | it('Should not throw an exception when apiKey and/or token is/are missing', function() { 145 | var session = { 146 | id: "Test", 147 | connection: _connection, 148 | apiKey: "", 149 | token: "" 150 | }; 151 | var options = { 152 | session: session, 153 | extensionID: "ExtensionIDTest" 154 | }; 155 | _screenShare = new ScreenSharingAccPack(_options); 156 | expect(_screenShare).not.to.be.null; 157 | expect(_screenShare.publisher).not.to.be.null; 158 | }); 159 | }); 160 | }); 161 | --------------------------------------------------------------------------------