├── .asf.yaml ├── .eslintrc.yml ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ ├── FEATURE_REQUEST.md │ └── SUPPORT_QUESTION.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── android.yml │ ├── chrome.yml │ ├── ios.yml │ └── lint.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── RELEASENOTES.md ├── package-lock.json ├── package.json ├── plugin.xml ├── src ├── android │ ├── Capture.java │ ├── FileHelper.java │ ├── FileProvider.java │ ├── PendingRequests.java │ └── res │ │ └── xml │ │ └── mediacapture_provider_paths.xml ├── browser │ └── CaptureProxy.js ├── ios │ ├── CDVCapture.bundle │ │ ├── controls_bg.png │ │ ├── controls_bg@2x.png │ │ ├── controls_bg@2x~ipad.png │ │ ├── controls_bg~ipad.png │ │ ├── de.lproj │ │ │ └── Localizable.strings │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ ├── es.lproj │ │ │ └── Localizable.strings │ │ ├── fr.lproj │ │ │ └── Localizable.strings │ │ ├── microphone-568h@2x~iphone.png │ │ ├── microphone.png │ │ ├── microphone@2x.png │ │ ├── microphone@2x~ipad.png │ │ ├── microphone~ipad.png │ │ ├── record_button.png │ │ ├── record_button@2x.png │ │ ├── record_button@2x~ipad.png │ │ ├── record_button~ipad.png │ │ ├── recording_bg.png │ │ ├── recording_bg@2x.png │ │ ├── recording_bg@2x~ipad.png │ │ ├── recording_bg~ipad.png │ │ ├── se.lproj │ │ │ └── Localizable.strings │ │ ├── stop_button.png │ │ ├── stop_button@2x.png │ │ ├── stop_button@2x~ipad.png │ │ └── stop_button~ipad.png │ ├── CDVCapture.h │ └── CDVCapture.m └── windows │ ├── CaptureProxy.js │ └── MediaFile.js ├── tests ├── package.json ├── plugin.xml └── tests.js ├── types └── index.d.ts └── www ├── CaptureAudioOptions.js ├── CaptureError.js ├── CaptureImageOptions.js ├── CaptureVideoOptions.js ├── ConfigurationData.js ├── MediaFile.js ├── MediaFileData.js ├── android └── init.js ├── capture.js └── helpers.js /.asf.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | github: 19 | description: Apache Cordova Media Capture Plugin 20 | homepage: https://cordova.apache.org/ 21 | 22 | labels: 23 | - android 24 | - cordova 25 | - hacktoberfest 26 | - ios 27 | - java 28 | - javascript 29 | - library 30 | - mobile 31 | - nodejs 32 | - objective-c 33 | 34 | features: 35 | wiki: false 36 | issues: true 37 | projects: true 38 | 39 | enabled_merge_buttons: 40 | squash: true 41 | merge: false 42 | rebase: false 43 | 44 | notifications: 45 | commits: commits@cordova.apache.org 46 | issues: issues@cordova.apache.org 47 | pullrequests_status: issues@cordova.apache.org 48 | pullrequests_comment: issues@cordova.apache.org 49 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | root: true 19 | extends: "@cordova/eslint-config/browser" 20 | 21 | overrides: 22 | - files: [tests/**/*.js] 23 | extends: "@cordova/eslint-config/node-tests" 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | # 4 | ## These files are binary and should be left untouched 5 | # 6 | 7 | # (binary is a macro for -text -diff) 8 | *.png binary 9 | *.jpg binary 10 | *.jpeg binary 11 | *.gif binary 12 | *.ico binary 13 | *.mov binary 14 | *.mp4 binary 15 | *.mp3 binary 16 | *.flv binary 17 | *.fla binary 18 | *.swf binary 19 | *.gz binary 20 | *.zip binary 21 | *.7z binary 22 | *.ttf binary 23 | *.eot binary 24 | *.woff binary 25 | *.pyc binary 26 | *.pdf binary 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ### Issue Type 7 | 8 | 9 | - [ ] Bug Report 10 | - [ ] Feature Request 11 | - [ ] Support Question 12 | 13 | ## Description 14 | 15 | ## Information 16 | 17 | 18 | ### Command or Code 19 | 20 | 21 | ### Environment, Platform, Device 22 | 23 | 24 | 25 | 26 | ### Version information 27 | 34 | 35 | 36 | 37 | ## Checklist 38 | 39 | 40 | - [ ] I searched for already existing GitHub issues about this 41 | - [ ] I updated all Cordova tooling to their most recent version 42 | - [ ] I included all the necessary information above 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: If something isn't working as expected. 4 | 5 | --- 6 | 7 | # Bug Report 8 | 9 | ## Problem 10 | 11 | ### What is expected to happen? 12 | 13 | 14 | 15 | ### What does actually happen? 16 | 17 | 18 | 19 | ## Information 20 | 21 | 22 | 23 | 24 | ### Command or Code 25 | 26 | 27 | 28 | 29 | ### Environment, Platform, Device 30 | 31 | 32 | 33 | 34 | ### Version information 35 | 42 | 43 | 44 | 45 | ## Checklist 46 | 47 | 48 | - [ ] I searched for existing GitHub issues 49 | - [ ] I updated all Cordova tooling to most recent version 50 | - [ ] I included all the necessary information above 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | about: A suggestion for a new functionality 4 | 5 | --- 6 | 7 | # Feature Request 8 | 9 | ## Motivation Behind Feature 10 | 11 | 12 | 13 | 14 | ## Feature Description 15 | 20 | 21 | 22 | 23 | ## Alternatives or Workarounds 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💬 Support Question 3 | about: If you have a question, please check out our Slack or StackOverflow! 4 | 5 | --- 6 | 7 | 8 | 9 | Apache Cordova uses GitHub Issues as a feature request and bug tracker _only_. 10 | For usage and support questions, please check out the resources below. Thanks! 11 | 12 | --- 13 | 14 | You can get answers to your usage and support questions about **Apache Cordova** on: 15 | 16 | * GitHub Discussions: https://github.com/apache/cordova/discussions 17 | * Slack Community Chat: https://cordova.slack.com (you can sign-up at https://s.apache.org/cordova-slack) 18 | * StackOverflow: https://stackoverflow.com/questions/tagged/cordova using the tag `cordova` 19 | 20 | --- 21 | 22 | If you are using a tool that uses Cordova internally, like e.g. Ionic, check their support channels: 23 | 24 | * **Ionic Framework** 25 | * [Ionic Community Forum](https://forum.ionicframework.com/) 26 | * [Ionic Discord](https://ionic.link/discord) 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Platforms affected 10 | 11 | 12 | 13 | ### Motivation and Context 14 | 15 | 16 | 17 | 18 | 19 | ### Description 20 | 21 | 22 | 23 | 24 | ### Testing 25 | 26 | 27 | 28 | 29 | ### Checklist 30 | 31 | - [ ] I've run the tests to see all new and existing tests pass 32 | - [ ] I added automated test coverage as appropriate for this change 33 | - [ ] Commit is prefixed with `(platform)` if this change only applies to one platform (e.g. `(android)`) 34 | - [ ] If this Pull Request resolves an issue, I linked to the issue in the text above (and used the correct [keyword to close issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/)) 35 | - [ ] I've updated the documentation if necessary 36 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Android Testsuite 19 | 20 | on: 21 | push: 22 | paths-ignore: 23 | - '**.md' 24 | - 'LICENSE' 25 | - '.eslint*' 26 | 27 | pull_request: 28 | paths-ignore: 29 | - '**.md' 30 | - 'LICENSE' 31 | - '.eslint*' 32 | 33 | jobs: 34 | test: 35 | name: Android ${{ matrix.versions.android }} Test 36 | runs-on: ubuntu-latest 37 | continue-on-error: true 38 | 39 | # hoist configurations to top that are expected to be updated 40 | env: 41 | # Storing a copy of the repo 42 | repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} 43 | 44 | node-version: 20 45 | 46 | # These are the default Java configurations used by most tests. 47 | # To customize these options, add "java-distro" or "java-version" to the strategy matrix with its overriding value. 48 | default_java-distro: temurin 49 | default_java-version: 17 50 | 51 | # These are the default Android System Image configurations used by most tests. 52 | # To customize these options, add "system-image-arch" or "system-image-target" to the strategy matrix with its overriding value. 53 | default_system-image-arch: x86_64 54 | default_system-image-target: google_apis # Most system images have a google_api option. Set this as default. 55 | 56 | # configurations for each testing strategy (test matrix) 57 | strategy: 58 | matrix: 59 | versions: 60 | - android: 7 61 | android-api: 24 62 | 63 | - android: 7.1 64 | android-api: 25 65 | 66 | - android: 8 67 | android-api: 26 68 | 69 | - android: 8.1 70 | android-api: 27 71 | system-image-arch: x86 72 | 73 | - android: 9 74 | android-api: 28 75 | 76 | - android: 10 77 | android-api: 29 78 | 79 | - android: 11 80 | android-api: 30 81 | 82 | - android: 12 83 | android-api: 31 84 | 85 | - android: 12L 86 | android-api: 32 87 | 88 | - android: 13 89 | android-api: 33 90 | 91 | - android: 14 92 | android-api: 34 93 | 94 | timeout-minutes: 60 95 | 96 | steps: 97 | - uses: actions/checkout@v4 98 | - uses: actions/setup-node@v4 99 | with: 100 | node-version: ${{ env.node-version }} 101 | - uses: actions/setup-java@v4 102 | env: 103 | java-version: ${{ matrix.versions.java-version == '' && env.default_java-version || matrix.versions.java-version }} 104 | java-distro: ${{ matrix.versions.java-distro == '' && env.default_java-distro || matrix.versions.java-distro }} 105 | with: 106 | distribution: ${{ env.java-distro }} 107 | java-version: ${{ env.java-version }} 108 | 109 | - name: Enable KVM group perms 110 | run: | 111 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 112 | sudo udevadm control --reload-rules 113 | sudo udevadm trigger --name-match=kvm 114 | 115 | - name: Run Environment Information 116 | run: | 117 | node --version 118 | npm --version 119 | java -version 120 | 121 | - name: Run npm install 122 | run: | 123 | export PATH="/usr/local/lib/android/sdk/platform-tools":$PATH 124 | export JAVA_HOME=$JAVA_HOME_11_X64 125 | npm i -g cordova@latest 126 | npm ci 127 | 128 | - name: Run paramedic install 129 | if: ${{ endswith(env.repo, '/cordova-paramedic') != true }} 130 | run: npm i -g github:apache/cordova-paramedic 131 | 132 | - uses: reactivecircus/android-emulator-runner@v2 133 | env: 134 | system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }} 135 | system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }} 136 | with: 137 | api-level: ${{ matrix.versions.android-api }} 138 | target: ${{ env.system-image-target }} 139 | arch: ${{ env.system-image-arch }} 140 | force-avd-creation: false 141 | disable-animations: false 142 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim 143 | script: echo "Pregenerate the AVD before running Paramedic" 144 | 145 | - name: Run paramedic tests 146 | uses: reactivecircus/android-emulator-runner@v2 147 | env: 148 | system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }} 149 | system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }} 150 | test_config: 'android-${{ matrix.versions.android }}.config.json' 151 | # Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed. 152 | test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }} 153 | paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }} 154 | with: 155 | api-level: ${{ matrix.versions.android-api }} 156 | target: ${{ env.system-image-target }} 157 | arch: ${{ env.system-image-arch }} 158 | force-avd-creation: false 159 | disable-animations: false 160 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim 161 | script: ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }} 162 | -------------------------------------------------------------------------------- /.github/workflows/chrome.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Chrome Testsuite 19 | 20 | on: 21 | push: 22 | paths-ignore: 23 | - '**.md' 24 | - 'LICENSE' 25 | - '.eslint*' 26 | pull_request: 27 | paths-ignore: 28 | - '**.md' 29 | - 'LICENSE' 30 | - '.eslint*' 31 | 32 | jobs: 33 | test: 34 | name: Chrome Latest Test 35 | runs-on: ubuntu-latest 36 | 37 | # hoist configurations to top that are expected to be updated 38 | env: 39 | # Storing a copy of the repo 40 | repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} 41 | 42 | node-version: 20 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions/setup-node@v4 47 | with: 48 | node-version: ${{ env.node-version }} 49 | 50 | - name: Run install xvfb 51 | run: sudo apt-get install xvfb 52 | 53 | - name: Run Environment Information 54 | run: | 55 | node --version 56 | npm --version 57 | 58 | - name: Run npm install 59 | run: | 60 | npm i -g cordova@latest 61 | npm ci 62 | 63 | - name: Run paramedic install 64 | if: ${{ endswith(env.repo, '/cordova-paramedic') != true }} 65 | run: npm i -g github:apache/cordova-paramedic 66 | 67 | - name: Run paramedic tests 68 | env: 69 | test_config: 'browser.config.json' 70 | # Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed. 71 | test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }} 72 | paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }} 73 | run: xvfb-run --auto-servernum ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }} 74 | -------------------------------------------------------------------------------- /.github/workflows/ios.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: iOS Testsuite 19 | 20 | on: 21 | push: 22 | paths-ignore: 23 | - '**.md' 24 | - 'LICENSE' 25 | - '.eslint*' 26 | 27 | pull_request: 28 | paths-ignore: 29 | - '**.md' 30 | - 'LICENSE' 31 | - '.eslint*' 32 | 33 | jobs: 34 | test: 35 | name: iOS ${{ matrix.versions.ios-version }} Test 36 | runs-on: ${{ matrix.versions.os-version }} 37 | continue-on-error: true 38 | 39 | # hoist configurations to top that are expected to be updated 40 | env: 41 | # Storing a copy of the repo 42 | repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }} 43 | 44 | node-version: 20 45 | 46 | # > Starting April 26, 2021, all iOS and iPadOS apps submitted to the App Store must be built with Xcode 12 and the iOS 14 SDK. 47 | # Because of Apple's requirement, listed above, We will only be using the latest Xcode release for testing. 48 | # To customize these options, add "xcode-version" to the strategy matrix with its overriding value. 49 | default_xcode-version: latest-stable 50 | 51 | strategy: 52 | matrix: 53 | versions: 54 | - os-version: macos-13 55 | ios-version: 16.x 56 | xcode-version: 14.x 57 | 58 | - os-version: macos-14 59 | ios-version: 17.x 60 | xcode-version: 15.x 61 | 62 | - os-version: macos-15 63 | ios-version: 18.x 64 | xcode-version: 16.x 65 | 66 | steps: 67 | - uses: actions/checkout@v4 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: ${{ env.node-version }} 71 | - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd 72 | env: 73 | xcode-version: ${{ matrix.versions.xcode-version == '' && env.default_xcode-version || matrix.versions.xcode-version }} 74 | with: 75 | xcode-version: ${{ env.xcode-version }} 76 | 77 | - name: Run Environment Information 78 | run: | 79 | node --version 80 | npm --version 81 | xcodebuild -version 82 | 83 | - name: Run npm install 84 | run: | 85 | npm i -g cordova@latest ios-deploy@latest 86 | npm ci 87 | 88 | - name: Run paramedic install 89 | if: ${{ endswith(env.repo, '/cordova-paramedic') != true }} 90 | run: npm i -g github:apache/cordova-paramedic 91 | 92 | - name: Run paramedic tests 93 | env: 94 | test_config: 'ios-${{ matrix.versions.ios-version }}.config.json' 95 | # Generally, this should automatically work for cordova-paramedic & plugins. If the path is unique, this can be manually changed. 96 | test_plugin_path: ${{ endswith(env.repo, '/cordova-paramedic') && './spec/testable-plugin/' || './' }} 97 | paramedic: ${{ endswith(env.repo, '/cordova-paramedic') && 'node main.js' || 'cordova-paramedic' }} 98 | run: ${{ env.paramedic }} --config ./pr/local/${{ env.test_config }} --plugin ${{ env.test_plugin_path }} 99 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: Lint Test 19 | 20 | on: 21 | push: 22 | paths: 23 | - '**.js' 24 | - '.eslint*' 25 | - '.github/workflow/lint.yml' 26 | pull_request: 27 | paths: 28 | - '**.js' 29 | - '.eslint*' 30 | - '.github/workflow/lint.yml' 31 | 32 | jobs: 33 | test: 34 | name: Lint Test 35 | runs-on: ubuntu-latest 36 | env: 37 | node-version: 20 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions/setup-node@v4 42 | with: 43 | node-version: ${{ env.node-version }} 44 | 45 | - name: Run Environment Information 46 | run: | 47 | node --version 48 | npm --version 49 | 50 | - name: Run npm install 51 | run: | 52 | npm ci 53 | 54 | - name: Run lint test 55 | run: | 56 | npm run lint 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #If ignorance is bliss, then somebody knock the smile off my face 2 | 3 | *.csproj.user 4 | *.suo 5 | *.cache 6 | Thumbs.db 7 | *.DS_Store 8 | 9 | *.bak 10 | *.cache 11 | *.log 12 | *.swp 13 | *.user 14 | 15 | node_modules 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | tests 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | # Contributing to Apache Cordova 23 | 24 | Anyone can contribute to Cordova. And we need your contributions. 25 | 26 | There are multiple ways to contribute: report bugs, improve the docs, and 27 | contribute code. 28 | 29 | For instructions on this, start with the 30 | [contribution overview](http://cordova.apache.org/contribute/). 31 | 32 | The details are explained there, but the important items are: 33 | - Check for Github issues that corresponds to your contribution and link or create them if necessary. 34 | - Run the tests so your patch doesn't break existing functionality. 35 | 36 | We look forward to your contributions! 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache Cordova 2 | Copyright 2012 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Media Capture 3 | description: Capture audio, video, and images. 4 | --- 5 | 23 | 24 | # cordova-plugin-media-capture 25 | 26 | [![Android Testsuite](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/android.yml/badge.svg)](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/android.yml) [![Chrome Testsuite](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/chrome.yml/badge.svg)](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/chrome.yml) [![iOS Testsuite](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/ios.yml/badge.svg)](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/ios.yml) [![Lint Test](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/lint.yml/badge.svg)](https://github.com/apache/cordova-plugin-media-capture/actions/workflows/lint.yml) 27 | 28 | This plugin provides access to the device's audio, image, and video capture capabilities. 29 | 30 | __WARNING__: Collection and use of images, video, or 31 | audio from the device's camera or microphone raises important privacy 32 | issues. Your app's privacy policy should discuss how the app uses 33 | such sensors and whether the data recorded is shared with any other 34 | parties. In addition, if the app's use of the camera or microphone is 35 | not apparent in the user interface, you should provide a just-in-time 36 | notice before the app accesses the camera or microphone (if the 37 | device operating system doesn't do so already). That notice should 38 | provide the same information noted above, as well as obtaining the 39 | user's permission (e.g., by presenting choices for __OK__ and __No 40 | Thanks__). Note that some app marketplaces may require your app to 41 | provide just-in-time notice and obtain permission from the user prior 42 | to accessing the camera or microphone. For more information, please 43 | see the Privacy Guide. 44 | 45 | This plugin defines global `navigator.device.capture` object. 46 | 47 | Although in the global scope, it is not available until after the `deviceready` event. 48 | 49 | document.addEventListener("deviceready", onDeviceReady, false); 50 | function onDeviceReady() { 51 | console.log(navigator.device.capture); 52 | } 53 | 54 | ## Installation 55 | 56 | cordova plugin add cordova-plugin-media-capture 57 | 58 | ## Supported Platforms 59 | 60 | - Android 61 | - Browser 62 | - iOS 63 | - Windows 64 | 65 | ## Objects 66 | 67 | - Capture 68 | - CaptureAudioOptions 69 | - CaptureImageOptions 70 | - CaptureVideoOptions 71 | - CaptureCallback 72 | - CaptureErrorCB 73 | - ConfigurationData 74 | - MediaFile 75 | - MediaFileData 76 | 77 | ## Methods 78 | 79 | - capture.captureAudio 80 | - capture.captureImage 81 | - capture.captureVideo 82 | - MediaFile.getFormatData 83 | 84 | ## Properties 85 | 86 | - __supportedAudioModes__: The audio recording formats supported by the device. (ConfigurationData[]) 87 | 88 | - __supportedImageModes__: The recording image sizes and formats supported by the device. (ConfigurationData[]) 89 | 90 | - __supportedVideoModes__: The recording video resolutions and formats supported by the device. (ConfigurationData[]) 91 | 92 | ## capture.captureAudio 93 | 94 | > Start the audio recorder application and return information about captured audio clip files. 95 | 96 | navigator.device.capture.captureAudio( 97 | CaptureCB captureSuccess, CaptureErrorCB captureError, [CaptureAudioOptions options] 98 | ); 99 | 100 | ### Description 101 | 102 | Starts an asynchronous operation to capture audio recordings using the 103 | device's default audio recording application. The operation allows 104 | the device user to capture multiple recordings in a single session. 105 | 106 | The capture operation ends when either the user exits the audio 107 | recording application, or the maximum number of recordings specified 108 | by `CaptureAudioOptions.limit` is reached. If no `limit` parameter 109 | value is specified, it defaults to one (1), and the capture operation 110 | terminates after the user records a single audio clip. 111 | 112 | When the capture operation finishes, the `CaptureCallback` executes 113 | with an array of `MediaFile` objects describing each captured audio 114 | clip file. If the user terminates the operation before an audio clip 115 | is captured, the `CaptureErrorCallback` executes with a `CaptureError` 116 | object, featuring the `CaptureError.CAPTURE_NO_MEDIA_FILES` error 117 | code. 118 | 119 | ### Supported Platforms 120 | 121 | - Android 122 | - iOS 123 | - Windows 124 | 125 | ### Example 126 | 127 | // capture callback 128 | var captureSuccess = function(mediaFiles) { 129 | var i, path, len; 130 | for (i = 0, len = mediaFiles.length; i < len; i += 1) { 131 | path = mediaFiles[i].fullPath; 132 | // do something interesting with the file 133 | } 134 | }; 135 | 136 | // capture error callback 137 | var captureError = function(error) { 138 | navigator.notification.alert('Error code: ' + error.code, null, 'Capture Error'); 139 | }; 140 | 141 | // start audio capture 142 | navigator.device.capture.captureAudio(captureSuccess, captureError, {limit:2}); 143 | 144 | ### iOS Quirks 145 | 146 | - iOS does not have a default audio recording application, so a simple user interface is provided. 147 | 148 | ### Windows Phone 7 and 8 Quirks 149 | 150 | - Windows Phone 7 does not have a default audio recording application, so a simple user interface is provided. 151 | 152 | ## capture.captureImage 153 | 154 | > Start the camera application and return information about captured image files. 155 | 156 | navigator.device.capture.captureImage( 157 | CaptureCB captureSuccess, CaptureErrorCB captureError, [CaptureImageOptions options] 158 | ); 159 | 160 | ### Description 161 | 162 | Starts an asynchronous operation to capture images using the device's 163 | camera application. The operation allows users to capture more than 164 | one image in a single session. 165 | 166 | The capture operation ends either when the user closes the camera 167 | application, or the maximum number of recordings specified by 168 | `CaptureImageOptions.limit` is reached. If no `limit` value is 169 | specified, it defaults to one (1), and the capture operation 170 | terminates after the user captures a single image. 171 | 172 | When the capture operation finishes, it invokes the `CaptureCB` 173 | callback with an array of `MediaFile` objects describing each captured 174 | image file. If the user terminates the operation before capturing an 175 | image, the `CaptureErrorCB` callback executes with a `CaptureError` 176 | object featuring a `CaptureError.CAPTURE_NO_MEDIA_FILES` error code. 177 | 178 | ### Supported Platforms 179 | 180 | - Android 181 | - Browser 182 | - iOS 183 | - Windows 184 | 185 | ### iOS Quirks 186 | 187 | Since iOS 10 it's mandatory to provide an usage description in the `info.plist` if trying to access privacy-sensitive data. When the system prompts the user to allow access, this usage description string will displayed as part of the permission dialog box, but if you didn't provide the usage description, the app will crash before showing the dialog. Also, Apple will reject apps that access private data but don't provide an usage description. 188 | 189 | This plugins requires the following usage descriptions: 190 | 191 | * `NSCameraUsageDescription` describes the reason the app accesses the user's camera. 192 | * `NSMicrophoneUsageDescription` describes the reason the app accesses the user's microphone. 193 | * `NSPhotoLibraryUsageDescriptionentry` describes the reason the app accesses the user's photo library. 194 | 195 | 196 | To add these entries into the `info.plist`, you can use the `edit-config` tag in the `config.xml` like this: 197 | 198 | ``` 199 | 200 | need camera access to take pictures 201 | 202 | ``` 203 | 204 | ``` 205 | 206 | need microphone access to record sounds 207 | 208 | ``` 209 | 210 | ``` 211 | 212 | need to photo library access to get pictures from there 213 | 214 | ``` 215 | 216 | ### Browser Quirks 217 | 218 | Works in Chrome, Firefox and Opera only (since IE and Safari doesn't supports 219 | navigator.getUserMedia API) 220 | 221 | Displaying images using captured file's URL available in Chrome/Opera only. 222 | Firefox stores captured images in IndexedDB storage (see File plugin documentation), 223 | and due to this the only way to show captured image is to read it and show using its DataURL. 224 | 225 | ### Example 226 | 227 | // capture callback 228 | var captureSuccess = function(mediaFiles) { 229 | var i, path, len; 230 | for (i = 0, len = mediaFiles.length; i < len; i += 1) { 231 | path = mediaFiles[i].fullPath; 232 | // do something interesting with the file 233 | } 234 | }; 235 | 236 | // capture error callback 237 | var captureError = function(error) { 238 | navigator.notification.alert('Error code: ' + error.code, null, 'Capture Error'); 239 | }; 240 | 241 | // start image capture 242 | navigator.device.capture.captureImage(captureSuccess, captureError, {limit:2}); 243 | 244 | ## capture.captureVideo 245 | 246 | > Start the video recorder application and return information about captured video clip files. 247 | 248 | navigator.device.capture.captureVideo( 249 | CaptureCB captureSuccess, CaptureErrorCB captureError, [CaptureVideoOptions options] 250 | ); 251 | 252 | ### Description 253 | 254 | Starts an asynchronous operation to capture video recordings using the 255 | device's video recording application. The operation allows the user 256 | to capture more than one recordings in a single session. 257 | 258 | The capture operation ends when either the user exits the video 259 | recording application, or the maximum number of recordings specified 260 | by `CaptureVideoOptions.limit` is reached. If no `limit` parameter 261 | value is specified, it defaults to one (1), and the capture operation 262 | terminates after the user records a single video clip. 263 | 264 | When the capture operation finishes, it the `CaptureCB` callback 265 | executes with an array of `MediaFile` objects describing each captured 266 | video clip file. If the user terminates the operation before 267 | capturing a video clip, the `CaptureErrorCB` callback executes with a 268 | `CaptureError` object featuring a 269 | `CaptureError.CAPTURE_NO_MEDIA_FILES` error code. 270 | 271 | ### Supported Platforms 272 | 273 | - Android 274 | - iOS 275 | - Windows 276 | 277 | ### Example 278 | 279 | // capture callback 280 | var captureSuccess = function(mediaFiles) { 281 | var i, path, len; 282 | for (i = 0, len = mediaFiles.length; i < len; i += 1) { 283 | path = mediaFiles[i].fullPath; 284 | // do something interesting with the file 285 | } 286 | }; 287 | 288 | // capture error callback 289 | var captureError = function(error) { 290 | navigator.notification.alert('Error code: ' + error.code, null, 'Capture Error'); 291 | }; 292 | 293 | // start video capture 294 | navigator.device.capture.captureVideo(captureSuccess, captureError, {limit:2}); 295 | 296 | 297 | 298 | ## CaptureAudioOptions 299 | 300 | > Encapsulates audio capture configuration options. 301 | 302 | ### Properties 303 | 304 | - __limit__: The maximum number of audio clips the device user can record in a single capture operation. The value must be greater than or equal to 1 (defaults to 1). 305 | 306 | - __duration__: The maximum duration of an audio sound clip, in seconds. 307 | 308 | ### Example 309 | 310 | // limit capture operation to 3 media files, no longer than 10 seconds each 311 | var options = { limit: 3, duration: 10 }; 312 | 313 | navigator.device.capture.captureAudio(captureSuccess, captureError, options); 314 | 315 | ### Android Quirks 316 | 317 | - The `duration` parameter is not supported. Recording lengths can't be limited programmatically. 318 | 319 | 320 | ### iOS Quirks 321 | 322 | - The `limit` parameter is not supported, so only one recording can be created for each invocation. 323 | 324 | 325 | ## CaptureImageOptions 326 | 327 | > Encapsulates image capture configuration options. 328 | 329 | ### Properties 330 | 331 | - __limit__: The maximum number of images the user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1). 332 | 333 | ### Example 334 | 335 | // limit capture operation to 3 images 336 | var options = { limit: 3 }; 337 | 338 | navigator.device.capture.captureImage(captureSuccess, captureError, options); 339 | 340 | ### iOS Quirks 341 | 342 | - The __limit__ parameter is not supported, and only one image is taken per invocation. 343 | 344 | 345 | ## CaptureVideoOptions 346 | 347 | > Encapsulates video capture configuration options. 348 | 349 | ### Properties 350 | 351 | - __limit__: The maximum number of video clips the device's user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1). 352 | 353 | - __duration__: The maximum duration of a video clip, in seconds. 354 | 355 | - __quality__: To allow capturing video at different qualities. A value of `1` ( the default ) means high quality and value of `0` means low quality, suitable for MMS messages. 356 | 357 | ### Example 358 | 359 | // limit capture operation to 3 video clips 360 | var options = { limit: 3 }; 361 | 362 | navigator.device.capture.captureVideo(captureSuccess, captureError, options); 363 | 364 | ### iOS Quirks 365 | 366 | - The __limit__ property is ignored. Only one video is recorded per invocation. 367 | 368 | - The __quality__ property can have a value of `0.5` for medium quality. 369 | 370 | - See [here](https://developer.apple.com/documentation/uikit/uiimagepickercontroller/1619154-videoquality?language=objc) for more details about the __quality__ property on iOS. 371 | 372 | ### Android Quirks 373 | 374 | - See [here](http://developer.android.com/reference/android/provider/MediaStore.html#EXTRA_VIDEO_QUALITY) for more details about the __quality__ property on Android. 375 | 376 | ### Example ( w/ quality ) 377 | 378 | // limit capture operation to 1 video clip of low quality 379 | var options = { limit: 1, quality: 0 }; 380 | navigator.device.capture.captureVideo(captureSuccess, captureError, options); 381 | 382 | 383 | ## CaptureCB 384 | 385 | > Invoked upon a successful media capture operation. 386 | 387 | function captureSuccess( MediaFile[] mediaFiles ) { ... }; 388 | 389 | ### Description 390 | 391 | This function executes after a successful capture operation completes. 392 | At this point a media file has been captured, and either the user has 393 | exited the media capture application, or the capture limit has been 394 | reached. 395 | 396 | Each `MediaFile` object describes a captured media file. 397 | 398 | ### Example 399 | 400 | // capture callback 401 | function captureSuccess(mediaFiles) { 402 | var i, path, len; 403 | for (i = 0, len = mediaFiles.length; i < len; i += 1) { 404 | path = mediaFiles[i].fullPath; 405 | // do something interesting with the file 406 | } 407 | }; 408 | 409 | ## CaptureError 410 | 411 | > Encapsulates the error code resulting from a failed media capture operation. 412 | 413 | ### Properties 414 | 415 | - __code__: One of the pre-defined error codes listed below. 416 | 417 | ### Constants 418 | 419 | - `CaptureError.CAPTURE_INTERNAL_ERR`: The camera or microphone failed to capture image or sound. 420 | 421 | - `CaptureError.CAPTURE_APPLICATION_BUSY`: The camera or audio capture application is currently serving another capture request. 422 | 423 | - `CaptureError.CAPTURE_INVALID_ARGUMENT`: Invalid use of the API (e.g., the value of `limit` is less than one). 424 | 425 | - `CaptureError.CAPTURE_NO_MEDIA_FILES`: The user exits the camera or audio capture application before capturing anything. 426 | 427 | - `CaptureError.CAPTURE_PERMISSION_DENIED`: The user denied a permission required to perform the given capture request. 428 | 429 | - `CaptureError.CAPTURE_NOT_SUPPORTED`: The requested capture operation is not supported. 430 | 431 | ## CaptureErrorCB 432 | 433 | > Invoked if an error occurs during a media capture operation. 434 | 435 | function captureError( CaptureError error ) { ... }; 436 | 437 | ### Description 438 | 439 | This function executes if an error occurs when trying to launch a 440 | media capture operation. Failure scenarios include when the capture 441 | application is busy, a capture operation is already taking place, or 442 | the user cancels the operation before any media files are captured. 443 | 444 | This function executes with a `CaptureError` object containing an 445 | appropriate error `code`. 446 | 447 | ### Example 448 | 449 | // capture error callback 450 | var captureError = function(error) { 451 | navigator.notification.alert('Error code: ' + error.code, null, 'Capture Error'); 452 | }; 453 | 454 | ## ConfigurationData 455 | 456 | > Encapsulates a set of media capture parameters that a device supports. 457 | 458 | ### Description 459 | 460 | Describes media capture modes supported by the device. The 461 | configuration data includes the MIME type, and capture dimensions for 462 | video or image capture. 463 | 464 | The MIME types should adhere to [RFC2046](http://www.ietf.org/rfc/rfc2046.txt). Examples: 465 | 466 | - `video/3gpp` 467 | - `video/quicktime` 468 | - `image/jpeg` 469 | - `audio/amr` 470 | - `audio/wav` 471 | 472 | ### Properties 473 | 474 | - __type__: The ASCII-encoded lowercase string representing the media type. (DOMString) 475 | 476 | - __height__: The height of the image or video in pixels. The value is zero for sound clips. (Number) 477 | 478 | - __width__: The width of the image or video in pixels. The value is zero for sound clips. (Number) 479 | 480 | ### Example 481 | 482 | // retrieve supported image modes 483 | var imageModes = navigator.device.capture.supportedImageModes; 484 | 485 | // Select mode that has the highest horizontal resolution 486 | var width = 0; 487 | var selectedmode; 488 | for each (var mode in imageModes) { 489 | if (mode.width > width) { 490 | width = mode.width; 491 | selectedmode = mode; 492 | } 493 | } 494 | 495 | Not supported by any platform. All configuration data arrays are empty. 496 | 497 | ## MediaFile.getFormatData 498 | 499 | > Retrieves format information about the media capture file. 500 | 501 | mediaFile.getFormatData( 502 | MediaFileDataSuccessCB successCallback, 503 | [MediaFileDataErrorCB errorCallback] 504 | ); 505 | 506 | ### Description 507 | 508 | This function asynchronously attempts to retrieve the format 509 | information for the media file. If successful, it invokes the 510 | `MediaFileDataSuccessCB` callback with a `MediaFileData` object. If 511 | the attempt fails, this function invokes the `MediaFileDataErrorCB` 512 | callback. 513 | 514 | ### Supported Platforms 515 | 516 | - Android 517 | - iOS 518 | - Windows 519 | 520 | 521 | ### Android Quirks 522 | 523 | The API to access media file format information is limited, so not all 524 | `MediaFileData` properties are supported. 525 | 526 | ### iOS Quirks 527 | 528 | The API to access media file format information is limited, so not all 529 | `MediaFileData` properties are supported. 530 | 531 | ## MediaFile 532 | 533 | > Encapsulates properties of a media capture file. 534 | 535 | ### Properties 536 | 537 | - __name__: The name of the file, without path information. (DOMString) 538 | 539 | - __fullPath__: The full path of the file, including the name. (DOMString) 540 | 541 | - __type__: The file's mime type (DOMString) 542 | 543 | - __lastModifiedDate__: The date and time when the file was last modified. (Date) 544 | 545 | - __size__: The size of the file, in bytes. (Number) 546 | 547 | ### Methods 548 | 549 | - __MediaFile.getFormatData__: Retrieves the format information of the media file. 550 | 551 | ## MediaFileData 552 | 553 | > Encapsulates format information about a media file. 554 | 555 | ### Properties 556 | 557 | - __codecs__: The actual format of the audio and video content. (DOMString) 558 | 559 | - __bitrate__: The average bitrate of the content. The value is zero for images. (Number) 560 | 561 | - __height__: The height of the image or video in pixels. The value is zero for audio clips. (Number) 562 | 563 | - __width__: The width of the image or video in pixels. The value is zero for audio clips. (Number) 564 | 565 | - __duration__: The length of the video or sound clip in seconds. The value is zero for images. (Number) 566 | 567 | ### Android Quirks 568 | 569 | Supports the following `MediaFileData` properties: 570 | 571 | - __codecs__: Not supported, and returns `null`. 572 | 573 | - __bitrate__: Not supported, and returns zero. 574 | 575 | - __height__: Supported: image and video files only. 576 | 577 | - __width__: Supported: image and video files only. 578 | 579 | - __duration__: Supported: audio and video files only. 580 | 581 | ### iOS Quirks 582 | 583 | Supports the following `MediaFileData` properties: 584 | 585 | - __codecs__: Not supported, and returns `null`. 586 | 587 | - __bitrate__: Supported on iOS4 devices for audio only. Returns zero for images and videos. 588 | 589 | - __height__: Supported: image and video files only. 590 | 591 | - __width__: Supported: image and video files only. 592 | 593 | - __duration__: Supported: audio and video files only. 594 | 595 | ## Android Lifecycle Quirks 596 | 597 | When capturing audio, video, or images on the Android platform, there is a chance that the 598 | application will get destroyed after the Cordova Webview is pushed to the background by 599 | the native capture application. See the [Android Lifecycle Guide][android-lifecycle] for 600 | a full description of the issue. In this case, the success and failure callbacks passed 601 | to the capture method will not be fired and instead the results of the call will be 602 | delivered via a document event that fires after the Cordova [resume event][resume-event]. 603 | 604 | In your app, you should subscribe to the two possible events like so: 605 | 606 | ```javascript 607 | function onDeviceReady() { 608 | // pendingcaptureresult is fired if the capture call is successful 609 | document.addEventListener('pendingcaptureresult', function(mediaFiles) { 610 | // Do something with result 611 | }); 612 | 613 | // pendingcaptureerror is fired if the capture call is unsuccessful 614 | document.addEventListener('pendingcaptureerror', function(error) { 615 | // Handle error case 616 | }); 617 | } 618 | 619 | // Only subscribe to events after deviceready fires 620 | document.addEventListener('deviceready', onDeviceReady); 621 | ``` 622 | 623 | It is up you to track what part of your code these results are coming from. Be sure to 624 | save and restore your app's state as part of the [pause][pause-event] and 625 | [resume][resume-event] events as appropriate. Please note that these events will only 626 | fire on the Android platform and only when the Webview was destroyed during a capture 627 | operation. 628 | 629 | [android-lifecycle]: http://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#lifecycle-guide 630 | [pause-event]: http://cordova.apache.org/docs/en/latest/cordova/events/events.html#pause 631 | [resume-event]: http://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume 632 | -------------------------------------------------------------------------------- /RELEASENOTES.md: -------------------------------------------------------------------------------- 1 | 21 | # Release Notes 22 | 23 | ### 6.0.0 (Feb 21, 2025) 24 | 25 | **Breaking Changes:** 26 | 27 | * [GH-308](https://github.com/apache/cordova-plugin-media-capture/pull/308) feat(android)!: remove unnecessary permissions 28 | * [GH-280](https://github.com/apache/cordova-plugin-media-capture/pull/280) feat(android)!: remove RECORD_AUDIO permission not requested at runtime 29 | * [GH-295](https://github.com/apache/cordova-plugin-media-capture/pull/295) fix(android)!: remove broad media permissions 30 | 31 | **Features:** 32 | 33 | * [GH-281](https://github.com/apache/cordova-plugin-media-capture/pull/281) feat(android): Field 'storagePermissions' may be 'final' 34 | 35 | **Fixes:** 36 | 37 | * [GH-307](https://github.com/apache/cordova-plugin-media-capture/pull/307) fix(android): Remove usage of Media Store queries 38 | * [GH-302](https://github.com/apache/cordova-plugin-media-capture/pull/302) fix(android): save media capture to File Provider 39 | 40 | **Chores, Refactoring, CI:** 41 | 42 | * [GH-306](https://github.com/apache/cordova-plugin-media-capture/pull/306) chore: upgrade eslint config and rolled dependencies 43 | * [GH-305](https://github.com/apache/cordova-plugin-media-capture/pull/305) chore: npmrc 44 | * [GH-284](https://github.com/apache/cordova-plugin-media-capture/pull/284) chore: update asf config 45 | * [GH-309](https://github.com/apache/cordova-plugin-media-capture/pull/309) refactor(android): cleanup unused or obsolete code 46 | * [GH-304](https://github.com/apache/cordova-plugin-media-capture/pull/304) ci(ios): update workflow environment 47 | * [GH-301](https://github.com/apache/cordova-plugin-media-capture/pull/301) ci: sync workflow w/ paramedic 48 | 49 | ### 5.0.0 (Aug 14, 2023) 50 | 51 | **Breaking Changes:** 52 | 53 | * [GH-274](https://github.com/apache/cordova-plugin-media-capture/pull/274) feat(android)!: bump file & **Android** requirements 54 | * [GH-262](https://github.com/apache/cordova-plugin-media-capture/pull/262) feat(android)!: support API 33+ permissions 55 | 56 | **Features:** 57 | 58 | * [GH-214](https://github.com/apache/cordova-plugin-media-capture/pull/214) feat(ios): support capture '`quality`' param for videos 59 | * [GH-256](https://github.com/apache/cordova-plugin-media-capture/pull/256) feat(ios): improve `Localizable.strings` & add `FR` 60 | 61 | **Fixes:** 62 | 63 | * [GH-184](https://github.com/apache/cordova-plugin-media-capture/pull/184) fix(ios): UI issues with main thread and added alert for permission. 64 | * [GH-279](https://github.com/apache/cordova-plugin-media-capture/pull/279) fix(ios): UI sizing of the audio capture controller according to parent view size 65 | * [GH-278](https://github.com/apache/cordova-plugin-media-capture/pull/278) fix(ios): `CAPTURE_APPLICATION_BUSY` error when dismissing modal by swipe 66 | * [GH-197](https://github.com/apache/cordova-plugin-media-capture/pull/197) fix(ios): set type attribute for captured audio 67 | * [GH-232](https://github.com/apache/cordova-plugin-media-capture/pull/232) fix(android): prevent app crash caused by NPE on intent data or `mediaFile` 68 | * [GH-195](https://github.com/apache/cordova-plugin-media-capture/pull/195) fix(MediaFiles): return missing '`lastModified`' and '`end`' attributes 69 | * [GH-212](https://github.com/apache/cordova-plugin-media-capture/pull/212) fix: use single version in `cordovaDependencies` 70 | * [GH-269](https://github.com/apache/cordova-plugin-media-capture/pull/269) fix(ios): set category before creating `AVAudioRecorder` 71 | 72 | **Other Changes:** 73 | 74 | * [GH-276](https://github.com/apache/cordova-plugin-media-capture/pull/276) dep: bump `@cordova/eslint-config@^5.0.0` w/ lint fix & `package-lock` rebuild 75 | * [GH-270](https://github.com/apache/cordova-plugin-media-capture/pull/270) chore: Update `SUPPORT_QUESTION.md` template 76 | * [GH-252](https://github.com/apache/cordova-plugin-media-capture/pull/252) chore(npm): rebuilt `package-lock` 77 | * [GH-273](https://github.com/apache/cordova-plugin-media-capture/pull/273) ci: sync github action workflow w/ paramedic base configs 78 | * [GH-254](https://github.com/apache/cordova-plugin-media-capture/pull/254) ci: sync workflow with paramedic 79 | * [GH-251](https://github.com/apache/cordova-plugin-media-capture/pull/251) ci(android): update java requirement for `cordova-android@11` 80 | 81 | ### 4.0.0 (May 25, 2022) 82 | 83 | * [GH-238](https://github.com/apache/cordova-plugin-media-capture/pull/238) dep!: bump `cordova-plugin-file@^7.0.0` 84 | * [GH-248](https://github.com/apache/cordova-plugin-media-capture/pull/248) test: remove `cordova-plugin-media` dependency 85 | * [GH-247](https://github.com/apache/cordova-plugin-media-capture/pull/247) chore: bump `cordovaDependencies` next next major cordova requirement 86 | * [GH-246](https://github.com/apache/cordova-plugin-media-capture/pull/246) chore: rebuilt `package-lock` w/ v2 87 | * [GH-192](https://github.com/apache/cordova-plugin-media-capture/pull/192) fix(android): Unify and fix permission check 88 | * [GH-231](https://github.com/apache/cordova-plugin-media-capture/pull/231) ci(ios): update workflow w/ **iOS** 15 89 | * [GH-230](https://github.com/apache/cordova-plugin-media-capture/pull/230) ci: add `action-badge` 90 | * [GH-229](https://github.com/apache/cordova-plugin-media-capture/pull/229) ci: remove `travis` & `appveyor` 91 | * [GH-228](https://github.com/apache/cordova-plugin-media-capture/pull/228) ci: add `gh-actions` workflows 92 | * [GH-200](https://github.com/apache/cordova-plugin-media-capture/pull/200) fix(android): remove unknown permission `android.permission.RECORD_VIDEO` 93 | * [GH-203](https://github.com/apache/cordova-plugin-media-capture/pull/203) ci: add node-14.x to workflow 94 | * [GH-193](https://github.com/apache/cordova-plugin-media-capture/pull/193) chore: clean up `package.json` 95 | * [GH-189](https://github.com/apache/cordova-plugin-media-capture/pull/189) ci(travis): update osx xcode image 96 | * [GH-177](https://github.com/apache/cordova-plugin-media-capture/pull/177) breaking(ios): remove code warnings 97 | * [GH-188](https://github.com/apache/cordova-plugin-media-capture/pull/188) ci(travis): updates **Android** API level 98 | * [GH-180](https://github.com/apache/cordova-plugin-media-capture/pull/180) chore: adds `package-lock` file 99 | * [GH-179](https://github.com/apache/cordova-plugin-media-capture/pull/179) refactor(eslint): use `cordova-eslint` /w fix 100 | * [GH-178](https://github.com/apache/cordova-plugin-media-capture/pull/178) chore(npm): use short notation in `package.json` 101 | * chore(asf): update git notification settings 102 | * Update CONTRIBUTING.md 103 | * [GH-165](https://github.com/apache/cordova-plugin-media-capture/pull/165) ci: updates Node.js versions 104 | * [GH-164](https://github.com/apache/cordova-plugin-media-capture/pull/164) chore(npm): improve ignore list 105 | * [GH-161](https://github.com/apache/cordova-plugin-media-capture/pull/161) Small javadoc fix 106 | * ci(travis): upgrade to node 8 107 | 108 | ### 3.0.3 (Jun 19, 2019) 109 | 110 | - fix(android): Catch ActivityNotFoundException ([#104](https://github.com/apache/cordova-plugin-media-capture/issues/104)) ([`f69ba2a`](https://github.com/apache/cordova-plugin-media-capture/commit/f69ba2a)) 111 | - fix(android): [CB-14260](https://issues.apache.org/jira/browse/CB-14260) (android) captureImage permission denial on android 8.1 ([#95](https://github.com/apache/cordova-plugin-media-capture/issues/95)) ([`3755f9f`](https://github.com/apache/cordova-plugin-media-capture/commit/3755f9f)) 112 | - docs: remove outdated translations ([`6422b2b`](https://github.com/apache/cordova-plugin-media-capture/commit/6422b2b)) 113 | - build: add `.npmignore` to remove unneeded files from npm package ([`586f917`](https://github.com/apache/cordova-plugin-media-capture/commit/586f917)) 114 | - build: add `.gitattributes` to force LF (instead of possible CRLF on Windows) ([`246ce57`](https://github.com/apache/cordova-plugin-media-capture/commit/246ce57)) 115 | - ci(travis): Update Travis CI configuration for new paramedic ([#134](https://github.com/apache/cordova-plugin-media-capture/issues/134)) ([`f59af25`](https://github.com/apache/cordova-plugin-media-capture/commit/f59af25)) 116 | - chore(github): Add or update GitHub pull request and issue template ([`e5a982e`](https://github.com/apache/cordova-plugin-media-capture/commit/e5a982e)) 117 | - docs: remove JIRA link ([`c3928c8`](https://github.com/apache/cordova-plugin-media-capture/commit/c3928c8)) 118 | - ci(travis): also accept terms for android sdk `android-27` ([`59966ac`](https://github.com/apache/cordova-plugin-media-capture/commit/59966ac)) 119 | - chore(types): Add CaptureError.CAPTURE_PERMISSION_DENIED to type declaration file [#98](https://github.com/apache/cordova-plugin-media-capture/issues/98) ([`eff0128`](https://github.com/apache/cordova-plugin-media-capture/commit/eff0128), [`5fb4917`](https://github.com/apache/cordova-plugin-media-capture/commit/5fb4917)) 120 | 121 | ### 3.0.2 (Apr 12, 2018) 122 | * [CB-13866](https://issues.apache.org/jira/browse/CB-13866): **iOS** fix Camera opens in portrait orientation on iphones 123 | 124 | ### 3.0.1 (Dec 27, 2017) 125 | * [CB-13707](https://issues.apache.org/jira/browse/CB-13707) Fix to allow 3.0.0 version install (#88) 126 | * Bump cordova-plugin-file dependency to 6.0.0 127 | 128 | ### 3.0.0 (Dec 15, 2017) 129 | * [CB-13669](https://issues.apache.org/jira/browse/CB-13669) : Remove deprecated plugins 130 | 131 | ### 2.0.0 (Nov 06, 2017) 132 | * [CB-13520](https://issues.apache.org/jira/browse/CB-13520) (all): Add 'protective' entry to `cordovaDependencies` 133 | * [CB-13266](https://issues.apache.org/jira/browse/CB-13266) (ios): Remove **iOS** usage descriptions 134 | * [CB-13473](https://issues.apache.org/jira/browse/CB-13473) (CI) Removed **Browser** builds from AppVeyor 135 | * [CB-13294](https://issues.apache.org/jira/browse/CB-13294) Remove `cordova-plugin-compat` 136 | * [CB-13299](https://issues.apache.org/jira/browse/CB-13299) (CI) Fix **Android** builds 137 | * [CB-12895](https://issues.apache.org/jira/browse/CB-12895) added `eslint` and removed `jshint` 138 | * [CB-13028](https://issues.apache.org/jira/browse/CB-13028) (CI) **Browser** builds on Travis and AppVeyor 139 | * [CB-12882](https://issues.apache.org/jira/browse/CB-12882) (ios): adds support for permissions checks for `captureVideo` and `captureImage` methods 140 | * [CB-12847](https://issues.apache.org/jira/browse/CB-12847) added `bugs` entry to `package.json`. 141 | 142 | ### 1.4.3 (Apr 27, 2017) 143 | * [CB-12622](https://issues.apache.org/jira/browse/CB-12622) Added **Android 6.0** build badges to `README` 144 | * [CB-12685](https://issues.apache.org/jira/browse/CB-12685) added `package.json` to tests folder 145 | 146 | ### 1.4.2 (Feb 28, 2017) 147 | * [CB-12353](https://issues.apache.org/jira/browse/CB-12353) Corrected merges usage in `plugin.xml` 148 | * [CB-12369](https://issues.apache.org/jira/browse/CB-12369) Add plugin typings from `DefinitelyTyped` 149 | * [CB-12363](https://issues.apache.org/jira/browse/CB-12363) Added build badges for **iOS 9.3** and **iOS 10.0** 150 | * [CB-12230](https://issues.apache.org/jira/browse/CB-12230) Removed **Windows 8.1** build badges 151 | 152 | ### 1.4.1 (Dec 07, 2016) 153 | * [CB-12224](https://issues.apache.org/jira/browse/CB-12224) Updated version and RELEASENOTES.md for release 1.4.1 154 | * [CB-10701](https://issues.apache.org/jira/browse/CB-10701) [CB-7117](https://issues.apache.org/jira/browse/CB-7117) android: Add manual test to check getting metadata 155 | * [CB-10489](https://issues.apache.org/jira/browse/CB-10489) Fix for MediaFile.getFormatData / audio recording support 156 | * [CB-10488](https://issues.apache.org/jira/browse/CB-10488) Fix for MediaFile.getFormatData 157 | * [CB-11996](https://issues.apache.org/jira/browse/CB-11996) Added a new manual test to capture multiple images 158 | * [CB-11995](https://issues.apache.org/jira/browse/CB-11995) (android) Do not pass a file URI to the camera 159 | * [CB-11917](https://issues.apache.org/jira/browse/CB-11917) - Remove pull request template checklist item: "iCLA has been submitted…" 160 | * [CB-11832](https://issues.apache.org/jira/browse/CB-11832) Incremented plugin version. 161 | 162 | ### 1.4.0 (Sep 08, 2016) 163 | * Add mandatory **iOS 10** privacy description for microphone 164 | * [CB-11795](https://issues.apache.org/jira/browse/CB-11795) Add 'protective' entry to cordovaDependencies 165 | * [CB-11821](https://issues.apache.org/jira/browse/CB-11821) (ios) Add mandatory **iOS 10** privacy description 166 | * Plugin uses `Android Log class` and not `Cordova LOG class` 167 | * Add badges for paramedic builds on Jenkins 168 | * [CB-11396](https://issues.apache.org/jira/browse/CB-11396) - Audio Media Capture Crashes if app stores file on external storage 169 | * Add pull request template. 170 | * [CB-11212](https://issues.apache.org/jira/browse/CB-11212) **iOS**: Explicitly set the bundle for images 171 | * [CB-10554](https://issues.apache.org/jira/browse/CB-10554) Implementing plugin `save/restore` API for Android 172 | * [CB-11165](https://issues.apache.org/jira/browse/CB-11165) removed peer dependency 173 | * [CB-10996](https://issues.apache.org/jira/browse/CB-10996) Adding front matter to README.md 174 | 175 | ### 1.3.0 (Apr 15, 2016) 176 | * Replace `PermissionHelper.java` with `cordova-plugin-compat` 177 | * CB-10670, [CB-10994](https://issues.apache.org/jira/browse/CB-10994) **Android**, Marshmallow permissions 178 | * [CB-10720](https://issues.apache.org/jira/browse/CB-10720) Fixing README for display on Cordova website 179 | * [CB-10636](https://issues.apache.org/jira/browse/CB-10636) Add `JSHint` for plugins 180 | * [CB-10690](https://issues.apache.org/jira/browse/CB-10690) **windows**, fix crash when user cancels/closes photo app 181 | 182 | ### 1.2.0 (Jan 15, 2016) 183 | * [CB-10100](https://issues.apache.org/jira/browse/CB-10100) updated file dependency to not grab new majors 184 | * [CB-8863](https://issues.apache.org/jira/browse/CB-8863) Fix block usage of self 185 | 186 | ### 1.1.0 (Nov 18, 2015) 187 | * [CB-10035](https://issues.apache.org/jira/browse/CB-10035) Updated `RELEASENOTES` to be newest to oldest 188 | * Fixing contribute link. 189 | * [CB-9249](https://issues.apache.org/jira/browse/CB-9249) Fix **iOS** warnings in Media Capture plugin 190 | * Document the quality property in **Android** quirks 191 | * Add `CaptureVideoOption` for quality 192 | 193 | ### 1.0.1 (Jun 17, 2015) 194 | * [CB-9128](https://issues.apache.org/jira/browse/CB-9128) cordova-plugin-media-capture documentation translation: cordova-plugin-media-capture 195 | * fix npm md issue 196 | 197 | ### 1.0.0 (Apr 15, 2015) 198 | * [CB-8746](https://issues.apache.org/jira/browse/CB-8746) bumped version of file dependency 199 | * [CB-8746](https://issues.apache.org/jira/browse/CB-8746) gave plugin major version bump 200 | * [CB-8747](https://issues.apache.org/jira/browse/CB-8747) updated dependency, added peer dependency 201 | * [CB-8683](https://issues.apache.org/jira/browse/CB-8683) changed plugin-id to pacakge-name 202 | * [CB-8653](https://issues.apache.org/jira/browse/CB-8653) properly updated translated docs to use new id 203 | * [CB-8653](https://issues.apache.org/jira/browse/CB-8653) updated translated docs to use new id 204 | * Use TRAVIS_BUILD_DIR, install paramedic by npm 205 | * docs: added Windows to supported platforms 206 | * [CB-8687](https://issues.apache.org/jira/browse/CB-8687) consolidate manifest targets 207 | * [CB-7963](https://issues.apache.org/jira/browse/CB-7963) Adds support for browser platform 208 | * [CB-8653](https://issues.apache.org/jira/browse/CB-8653) Updated Readme 209 | * [CB-8659](https://issues.apache.org/jira/browse/CB-8659): ios: 4.0.x Compatibility: Remove use of initWebView method 210 | * [CB-8571](https://issues.apache.org/jira/browse/CB-8571) Integrate TravisCI 211 | * [CB-8438](https://issues.apache.org/jira/browse/CB-8438) cordova-plugin-media-capture documentation translation: cordova-plugin-media-capture 212 | * [CB-8538](https://issues.apache.org/jira/browse/CB-8538) Added package.json file 213 | 214 | ### 0.3.6 (Feb 04, 2015) 215 | * [CB-8351](https://issues.apache.org/jira/browse/CB-8351) ios: Use inline copies of deprecated CDV_IsIpad and CDV_IsIphone5 216 | * [CB-8351](https://issues.apache.org/jira/browse/CB-8351) ios: Stop using (newly) deprecated CDVJSON.h 217 | * [CB-8351](https://issues.apache.org/jira/browse/CB-8351) ios: Use argumentForIndex rather than NSArray extension 218 | * [CB-7977](https://issues.apache.org/jira/browse/CB-7977) Mention deviceready in plugin docs 219 | 220 | ### 0.3.5 (Dec 02, 2014) 221 | * [CB-7597](https://issues.apache.org/jira/browse/CB-7597) - `Localizable.strings` for Media Capture are in the default template, it should be in the plugin 222 | * [CB-7700](https://issues.apache.org/jira/browse/CB-7700) cordova-plugin-media-capture documentation translation: cordova-plugin-media-capture 223 | 224 | ### 0.3.4 (Oct 03, 2014) 225 | * [CB-7453](https://issues.apache.org/jira/browse/CB-7453) Adds fallback to m4a audio format when mp3 recording fails. 226 | * [CB-7429](https://issues.apache.org/jira/browse/CB-7429) Fixes image capture manual tests on windows 227 | * [CB-7429](https://issues.apache.org/jira/browse/CB-7429) Move windows8 and windows Proxies into one file 228 | * [CB-7429](https://issues.apache.org/jira/browse/CB-7429) Adds media capture support for windows 229 | 230 | ### 0.3.3 (Sep 17, 2014) 231 | * Renamed test dir, added nested `plugin.xml` 232 | * added documentation for manual tests 233 | * [CB-6959](https://issues.apache.org/jira/browse/CB-6959) Added manual tests 234 | * [CB-6959](https://issues.apache.org/jira/browse/CB-6959) Port capture tests to plugin-test-framework 235 | 236 | ### 0.3.2 (Aug 06, 2014) 237 | * ubuntu: fix compler warnings 238 | * ubuntu: support qt 5.2 239 | * [CB-6127](https://issues.apache.org/jira/browse/CB-6127) Updated translations for docs 240 | * [CB-6978](https://issues.apache.org/jira/browse/CB-6978) captureImage() function fails in Android 241 | * [CB-6890](https://issues.apache.org/jira/browse/CB-6890): Fix pluginManager access for 4.0.x branch 242 | 243 | ### 0.3.1 (Jun 05, 2014) 244 | * Added translations to documentation. Github close #14 245 | * Remove deprecated symbols for iOS < 6 246 | * Fixes captureTasks UI URIs 247 | * [CB-6808](https://issues.apache.org/jira/browse/CB-6808) Add license 248 | * [CB-6706](https://issues.apache.org/jira/browse/CB-6706): Relax dependency on file plugin 249 | * [CB-6491](https://issues.apache.org/jira/browse/CB-6491) add CONTRIBUTING.md 250 | 251 | ### 0.3.0 (Apr 17, 2014) 252 | * [CB-6152](https://issues.apache.org/jira/browse/CB-6152): [ios, android] Make mediafile compatible with file plugin 253 | * [CB-6385](https://issues.apache.org/jira/browse/CB-6385): Specify file plugin dependency version 254 | * [CB-6212](https://issues.apache.org/jira/browse/CB-6212): [iOS] fix warnings compiled under arm64 64-bit 255 | * [CB-6016](https://issues.apache.org/jira/browse/CB-6016) [BlackBerry10] Add audio capture capability 256 | * [Blackberry10] Add rim xml namespaces declaration 257 | * [CB-6422](https://issues.apache.org/jira/browse/CB-6422) [windows8] use cordova/exec/proxy 258 | * [CB-6460](https://issues.apache.org/jira/browse/CB-6460): Update license headers 259 | * Add NOTICE file 260 | 261 | ### 0.2.8 (Feb 26, 2014) 262 | * [CB-5202](https://issues.apache.org/jira/browse/CB-5202) Fix video capture crash on Android 4.3+ 263 | 264 | ### 0.2.7 (Feb 05, 2014) 265 | * [ubuntu] request audio/camera/microphone permission 266 | * fixed cordova cli add capture plugin not work wp 267 | * [CB-5685](https://issues.apache.org/jira/browse/CB-5685) [BlackBerry10] Add access_shared permission 268 | 269 | ### 0.2.6 (Jan 02, 2014) 270 | * [CB-5658](https://issues.apache.org/jira/browse/CB-5658) Add doc/index.md for Media Capture plugin 271 | * [CB-5569](https://issues.apache.org/jira/browse/CB-5569) Windows8. MediaFile constructor does not exist 272 | * [CB-5517](https://issues.apache.org/jira/browse/CB-5517) Fix the audio capture IO exception by putting it in a runnable 273 | 274 | ### 0.2.5 (Dec 4, 2013) 275 | * add ubuntu platform 276 | * Added amazon-fireos platform. Change to use amazon-fireos as a platform if user agent string contains 'cordova-amazon-fireos' 277 | * [CB-5291](https://issues.apache.org/jira/browse/CB-5291) - ios - Media Capture Audio - status bar issues under iOS 7 278 | * [CB-5275](https://issues.apache.org/jira/browse/CB-5275): CaptureImage and CaptureVideo have runnables and CaptureVideo works on 4.2. Still doesn't work for 4.3 279 | 280 | ### 0.2.4 (Oct 28, 2013) 281 | * [CB-5199](https://issues.apache.org/jira/browse/CB-5199) - ios - Media Capture - UI issues under iOS 7 282 | * [CB-5128](https://issues.apache.org/jira/browse/CB-5128): added repo + issue tag to `plugin.xml` for media capture plugin 283 | * [CB-5010](https://issues.apache.org/jira/browse/CB-5010) Incremented plugin version on dev branch. 284 | 285 | ### 0.2.3 (Oct 9, 2013) 286 | * [CB-4720](https://issues.apache.org/jira/browse/CB-4720): fixed incorrect feature tag in `plugin.xml` for wp 287 | * [CB-4915](https://issues.apache.org/jira/browse/CB-4915) Incremented plugin version on dev branch. 288 | 289 | ### 0.2.2 (Sept 25, 2013) 290 | * [CB-4889](https://issues.apache.org/jira/browse/CB-4889) bumping&resetting version 291 | * [windows8] commandProxy was moved 292 | * [windows8] commandProxy was moved 293 | * [CB-4889](https://issues.apache.org/jira/browse/CB-4889) 294 | * [CB-4889](https://issues.apache.org/jira/browse/CB-4889) renaming org.apache.cordova.core.media-capture to org.apache.cordova.media-capture and updating dependency 295 | * Rename CHANGELOG.md -> RELEASENOTES.md 296 | * [CB-4847](https://issues.apache.org/jira/browse/CB-4847) iOS 7 microphone access requires user permission - if denied, CDVCapture, CDVSound does not handle it properly 297 | * [CB-4826](https://issues.apache.org/jira/browse/CB-4826) Fix warning using UITextAlignmentCenter 298 | * [CB-4826](https://issues.apache.org/jira/browse/CB-4826) Fix XCode 5 capture plugin warnings 299 | * [CB-4488](https://issues.apache.org/jira/browse/CB-4488) - added manual capture test 300 | * [CB-4764](https://issues.apache.org/jira/browse/CB-4764) Remove reference to DirectoryManager from Capture.java 301 | * [CB-4763](https://issues.apache.org/jira/browse/CB-4763) Use own version of FileHelper. 302 | * [CB-4752](https://issues.apache.org/jira/browse/CB-4752) Incremented plugin version on dev branch. 303 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-media-capture", 3 | "version": "6.0.1-dev", 4 | "description": "Cordova Media Capture Plugin", 5 | "types": "./types/index.d.ts", 6 | "cordova": { 7 | "id": "cordova-plugin-media-capture", 8 | "platforms": [ 9 | "android", 10 | "ios", 11 | "windows", 12 | "browser" 13 | ] 14 | }, 15 | "repository": "github:apache/cordova-plugin-media-capture", 16 | "bugs": "https://github.com/apache/cordova-plugin-media-capture/issues", 17 | "keywords": [ 18 | "cordova", 19 | "media", 20 | "capture", 21 | "ecosystem:cordova", 22 | "cordova-android", 23 | "cordova-ios", 24 | "cordova-windows" 25 | ], 26 | "scripts": { 27 | "test": "npm run lint", 28 | "lint": "eslint ." 29 | }, 30 | "author": "Apache Software Foundation", 31 | "license": "Apache-2.0", 32 | "engines": { 33 | "cordovaDependencies": { 34 | "1.4.4": { 35 | "cordova-ios": ">=4.0.0" 36 | }, 37 | "2.0.0": { 38 | "cordova-android": ">=6.3.0" 39 | }, 40 | "4.0.0": { 41 | "cordova-android": ">=10.0.0" 42 | }, 43 | "5.0.0": { 44 | "cordova-android": ">=12.0.0" 45 | }, 46 | "6.0.0": { 47 | "cordova-android": ">=12.0.0" 48 | }, 49 | "7.0.0": { 50 | "cordova": ">100" 51 | } 52 | } 53 | }, 54 | "devDependencies": { 55 | "@cordova/eslint-config": "^5.1.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 25 | Capture 26 | 27 | Cordova Media Capture Plugin 28 | Apache 2.0 29 | cordova,media,capture 30 | https://github.com/apache/cordova-plugin-media-capture 31 | https://github.com/apache/cordova-plugin-media-capture/issues 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/android/Capture.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.cordova.mediacapture; 20 | 21 | import java.io.File; 22 | import java.io.FileNotFoundException; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.OutputStream; 27 | import java.lang.reflect.Field; 28 | import java.lang.reflect.InvocationTargetException; 29 | import java.lang.reflect.Method; 30 | import java.text.SimpleDateFormat; 31 | import java.util.Arrays; 32 | import java.util.Date; 33 | 34 | import org.apache.cordova.CallbackContext; 35 | import org.apache.cordova.CordovaPlugin; 36 | import org.apache.cordova.LOG; 37 | import org.apache.cordova.PermissionHelper; 38 | import org.apache.cordova.PluginManager; 39 | import org.apache.cordova.file.FileUtils; 40 | import org.apache.cordova.file.LocalFilesystemURL; 41 | import org.apache.cordova.mediacapture.PendingRequests.Request; 42 | import org.json.JSONArray; 43 | import org.json.JSONException; 44 | import org.json.JSONObject; 45 | 46 | import android.Manifest; 47 | import android.app.Activity; 48 | import android.content.ActivityNotFoundException; 49 | import android.content.Intent; 50 | import android.content.pm.PackageManager; 51 | import android.content.pm.PackageManager.NameNotFoundException; 52 | import android.graphics.BitmapFactory; 53 | import android.media.MediaPlayer; 54 | import android.net.Uri; 55 | import android.os.Build; 56 | import android.os.Bundle; 57 | import android.os.Environment; 58 | import android.system.Os; 59 | import android.system.OsConstants; 60 | 61 | public class Capture extends CordovaPlugin { 62 | 63 | private static final String VIDEO_3GPP = "video/3gpp"; 64 | private static final String VIDEO_MP4 = "video/mp4"; 65 | private static final String AUDIO_3GPP = "audio/3gpp"; 66 | private static final String[] AUDIO_TYPES = new String[] {"audio/3gpp", "audio/aac", "audio/amr", "audio/wav"}; 67 | private static final String IMAGE_JPEG = "image/jpeg"; 68 | 69 | private static final int CAPTURE_AUDIO = 0; // Constant for capture audio 70 | private static final int CAPTURE_IMAGE = 1; // Constant for capture image 71 | private static final int CAPTURE_VIDEO = 2; // Constant for capture video 72 | private static final String LOG_TAG = "Capture"; 73 | 74 | private static final int CAPTURE_INTERNAL_ERR = 0; 75 | // private static final int CAPTURE_APPLICATION_BUSY = 1; 76 | // private static final int CAPTURE_INVALID_ARGUMENT = 2; 77 | private static final int CAPTURE_NO_MEDIA_FILES = 3; 78 | private static final int CAPTURE_PERMISSION_DENIED = 4; 79 | private static final int CAPTURE_NOT_SUPPORTED = 20; 80 | 81 | private boolean cameraPermissionInManifest; // Whether or not the CAMERA permission is declared in AndroidManifest.xml 82 | 83 | private final PendingRequests pendingRequests = new PendingRequests(); 84 | 85 | private String audioAbsolutePath; 86 | private String imageAbsolutePath; 87 | private String videoAbsolutePath; 88 | 89 | private String applicationId; 90 | 91 | @Override 92 | protected void pluginInitialize() { 93 | super.pluginInitialize(); 94 | 95 | // CB-10670: The CAMERA permission does not need to be requested unless it is declared 96 | // in AndroidManifest.xml. This plugin does not declare it, but others may and so we must 97 | // check the package info to determine if the permission is present. 98 | 99 | cameraPermissionInManifest = false; 100 | try { 101 | PackageManager packageManager = this.cordova.getActivity().getPackageManager(); 102 | String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions; 103 | if (permissionsInPackage != null) { 104 | for (String permission : permissionsInPackage) { 105 | if (permission.equals(Manifest.permission.CAMERA)) { 106 | cameraPermissionInManifest = true; 107 | break; 108 | } 109 | } 110 | } 111 | } catch (NameNotFoundException e) { 112 | // We are requesting the info for our package, so this should 113 | // never be caught 114 | LOG.e(LOG_TAG, "Failed checking for CAMERA permission in manifest", e); 115 | } 116 | } 117 | 118 | @Override 119 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 120 | this.applicationId = cordova.getContext().getPackageName(); 121 | 122 | if (action.equals("getFormatData")) { 123 | JSONObject obj = getFormatData(args.getString(0), args.getString(1)); 124 | callbackContext.success(obj); 125 | return true; 126 | } 127 | 128 | JSONObject options = args.optJSONObject(0); 129 | 130 | if (action.equals("captureAudio")) { 131 | this.captureAudio(pendingRequests.createRequest(CAPTURE_AUDIO, options, callbackContext)); 132 | } else if (action.equals("captureImage")) { 133 | this.captureImage(pendingRequests.createRequest(CAPTURE_IMAGE, options, callbackContext)); 134 | } else if (action.equals("captureVideo")) { 135 | this.captureVideo(pendingRequests.createRequest(CAPTURE_VIDEO, options, callbackContext)); 136 | } else { 137 | return false; 138 | } 139 | 140 | return true; 141 | } 142 | 143 | /** 144 | * Provides the media data file data depending on it's mime type 145 | * 146 | * @param filePath path to the file 147 | * @param mimeType of the file 148 | * @return a MediaFileData object 149 | */ 150 | private JSONObject getFormatData(String filePath, String mimeType) throws JSONException { 151 | Uri fileUrl = filePath.startsWith("file:") ? Uri.parse(filePath) : Uri.fromFile(new File(filePath)); 152 | JSONObject obj = new JSONObject(); 153 | // setup defaults 154 | obj.put("height", 0); 155 | obj.put("width", 0); 156 | obj.put("bitrate", 0); 157 | obj.put("duration", 0); 158 | obj.put("codecs", ""); 159 | 160 | // If the mimeType isn't set the rest will fail 161 | // so let's see if we can determine it. 162 | if (mimeType == null || mimeType.isEmpty() || "null".equals(mimeType)) { 163 | mimeType = FileHelper.getMimeType(fileUrl, cordova); 164 | } 165 | LOG.d(LOG_TAG, "Mime type = " + mimeType); 166 | 167 | if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) { 168 | obj = getImageData(fileUrl, obj); 169 | } else if (Arrays.asList(AUDIO_TYPES).contains(mimeType)) { 170 | obj = getAudioVideoData(filePath, obj, false); 171 | } else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) { 172 | obj = getAudioVideoData(filePath, obj, true); 173 | } 174 | return obj; 175 | } 176 | 177 | /** 178 | * Get the Image specific attributes 179 | * 180 | * @param fileUrl url pointing to the file 181 | * @param obj represents the Media File Data 182 | * @return a JSONObject that represents the Media File Data 183 | * @throws JSONException 184 | */ 185 | private JSONObject getImageData(Uri fileUrl, JSONObject obj) throws JSONException { 186 | BitmapFactory.Options options = new BitmapFactory.Options(); 187 | options.inJustDecodeBounds = true; 188 | BitmapFactory.decodeFile(fileUrl.getPath(), options); 189 | obj.put("height", options.outHeight); 190 | obj.put("width", options.outWidth); 191 | return obj; 192 | } 193 | 194 | /** 195 | * Get the Image specific attributes 196 | * 197 | * @param filePath path to the file 198 | * @param obj represents the Media File Data 199 | * @param video if true get video attributes as well 200 | * @return a JSONObject that represents the Media File Data 201 | * @throws JSONException 202 | */ 203 | private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { 204 | MediaPlayer player = new MediaPlayer(); 205 | try { 206 | player.setDataSource(filePath); 207 | player.prepare(); 208 | obj.put("duration", player.getDuration() / 1000); 209 | if (video) { 210 | obj.put("height", player.getVideoHeight()); 211 | obj.put("width", player.getVideoWidth()); 212 | } 213 | } catch (IOException e) { 214 | LOG.d(LOG_TAG, "Error: loading video file"); 215 | } 216 | return obj; 217 | } 218 | 219 | private String getTempDirectoryPath() { 220 | File cache = new File(cordova.getActivity().getCacheDir(), "org.apache.cordova.mediacapture"); 221 | 222 | // Create the cache directory if it doesn't exist 223 | cache.mkdirs(); 224 | return cache.getAbsolutePath(); 225 | } 226 | 227 | /** 228 | * Sets up an intent to capture audio. Result handled by onActivityResult() 229 | */ 230 | private void captureAudio(Request req) { 231 | try { 232 | Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); 233 | String timeStamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); 234 | String fileName = "cdv_media_capture_audio_" + timeStamp + ".m4a"; 235 | File audio = new File(getTempDirectoryPath(), fileName); 236 | Uri audioUri = FileProvider.getUriForFile(this.cordova.getActivity(), 237 | this.applicationId + ".cordova.plugin.mediacapture.provider", 238 | audio); 239 | this.audioAbsolutePath = audio.getAbsolutePath(); 240 | LOG.d(LOG_TAG, "Recording an audio and saving to: " + this.audioAbsolutePath); 241 | 242 | this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); 243 | } catch (ActivityNotFoundException ex) { 244 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NOT_SUPPORTED, "No Activity found to handle Audio Capture.")); 245 | } 246 | } 247 | 248 | /** 249 | * Checks for and requests the camera permission if necessary. 250 | * 251 | * Returns a boolean which if true, signals that the permission has been granted, or that the 252 | * permission isn't necessary and that the action may continue as normal. 253 | * 254 | * If the response is false, then the action should stop performing, as a permission prompt 255 | * will be presented to the user. The action based on the request's requestCode will be invoked 256 | * later. 257 | * 258 | * @param req 259 | * @return 260 | */ 261 | private boolean requestCameraPermission(Request req) { 262 | boolean cameraPermissionGranted = true; // We will default to true, but if the manifest 263 | // declares the permission, then we need to check 264 | // for the grant 265 | if (cameraPermissionInManifest) { 266 | cameraPermissionGranted = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA); 267 | } 268 | 269 | if (!cameraPermissionGranted) { 270 | PermissionHelper.requestPermissions(this, req.requestCode, new String[]{Manifest.permission.CAMERA}); 271 | return false; 272 | } 273 | 274 | return true; 275 | } 276 | 277 | /** 278 | * Sets up an intent to capture images. Result handled by onActivityResult() 279 | */ 280 | private void captureImage(Request req) { 281 | if (!requestCameraPermission(req)) { 282 | return; 283 | } 284 | 285 | Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); 286 | 287 | String timeStamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); 288 | String fileName = "cdv_media_capture_image_" + timeStamp + ".jpg"; 289 | File image = new File(getTempDirectoryPath(), fileName); 290 | 291 | Uri imageUri = FileProvider.getUriForFile(this.cordova.getActivity(), 292 | this.applicationId + ".cordova.plugin.mediacapture.provider", 293 | image); 294 | this.imageAbsolutePath = image.getAbsolutePath(); 295 | intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri); 296 | LOG.d(LOG_TAG, "Taking a picture and saving to: " + this.imageAbsolutePath); 297 | 298 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 299 | 300 | intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri); 301 | 302 | this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); 303 | } 304 | 305 | /** 306 | * Sets up an intent to capture video. Result handled by onActivityResult() 307 | */ 308 | private void captureVideo(Request req) { 309 | if (!requestCameraPermission(req)) { 310 | return; 311 | } 312 | 313 | Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); 314 | String timeStamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); 315 | String fileName = "cdv_media_capture_video_" + timeStamp + ".mp4"; 316 | File movie = new File(getTempDirectoryPath(), fileName); 317 | 318 | Uri videoUri = FileProvider.getUriForFile(this.cordova.getActivity(), 319 | this.applicationId + ".cordova.plugin.mediacapture.provider", 320 | movie); 321 | this.videoAbsolutePath = movie.getAbsolutePath(); 322 | intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, videoUri); 323 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 324 | LOG.d(LOG_TAG, "Recording a video and saving to: " + this.videoAbsolutePath); 325 | intent.putExtra("android.intent.extra.durationLimit", req.duration); 326 | intent.putExtra("android.intent.extra.videoQuality", req.quality); 327 | this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); 328 | } 329 | 330 | /** 331 | * Called when the video view exits. 332 | * 333 | * @param requestCode The request code originally supplied to startActivityForResult(), 334 | * allowing you to identify who this result came from. 335 | * @param resultCode The integer result code returned by the child activity through its setResult(). 336 | * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). 337 | * @throws JSONException 338 | */ 339 | public void onActivityResult(int requestCode, int resultCode, final Intent intent) { 340 | final Request req = pendingRequests.get(requestCode); 341 | 342 | // Result received okay 343 | if (resultCode == Activity.RESULT_OK) { 344 | Runnable processActivityResult = new Runnable() { 345 | @Override 346 | public void run() { 347 | switch(req.action) { 348 | case CAPTURE_AUDIO: 349 | onAudioActivityResult(req, intent); 350 | break; 351 | case CAPTURE_IMAGE: 352 | onImageActivityResult(req); 353 | break; 354 | case CAPTURE_VIDEO: 355 | onVideoActivityResult(req); 356 | break; 357 | } 358 | } 359 | }; 360 | 361 | this.cordova.getThreadPool().execute(processActivityResult); 362 | } 363 | // If canceled 364 | else if (resultCode == Activity.RESULT_CANCELED) { 365 | // If we have partial results send them back to the user 366 | if (req.results.length() > 0) { 367 | pendingRequests.resolveWithSuccess(req); 368 | } 369 | // user canceled the action 370 | else { 371 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NO_MEDIA_FILES, "Canceled.")); 372 | } 373 | } 374 | // If something else 375 | else { 376 | // If we have partial results send them back to the user 377 | if (req.results.length() > 0) { 378 | pendingRequests.resolveWithSuccess(req); 379 | } 380 | // something bad happened 381 | else { 382 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NO_MEDIA_FILES, "Did not complete!")); 383 | } 384 | } 385 | } 386 | 387 | public void onAudioActivityResult(Request req, Intent intent) { 388 | Uri uri = intent.getData(); 389 | 390 | InputStream input = null; 391 | OutputStream output = null; 392 | try { 393 | if (uri == null) { 394 | throw new IOException("Unable to open input audio"); 395 | } 396 | 397 | input = this.cordova.getActivity().getContentResolver().openInputStream(uri); 398 | 399 | if (input == null) { 400 | throw new IOException("Unable to open input audio"); 401 | } 402 | 403 | output = new FileOutputStream(this.audioAbsolutePath); 404 | 405 | byte[] buffer = new byte[getPageSize()]; 406 | int bytesRead; 407 | while ((bytesRead = input.read(buffer)) != -1) { 408 | output.write(buffer, 0, bytesRead); 409 | } 410 | } catch (FileNotFoundException e) { 411 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error: Unable to read input audio: File not found")); 412 | } catch (IOException e) { 413 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error: Unable to read input audio")); 414 | } finally { 415 | try { 416 | if (output != null) output.close(); 417 | if (input != null) input.close(); 418 | } catch (IOException ex) { 419 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error: Unable to copy input audio")); 420 | } 421 | } 422 | 423 | // create a file object from the audio absolute path 424 | JSONObject mediaFile = createMediaFileWithAbsolutePath(this.audioAbsolutePath); 425 | if (mediaFile == null) { 426 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + this.audioAbsolutePath)); 427 | return; 428 | } 429 | 430 | req.results.put(mediaFile); 431 | 432 | if (req.results.length() >= req.limit) { 433 | // Send Uri back to JavaScript for listening to audio 434 | pendingRequests.resolveWithSuccess(req); 435 | } else { 436 | // still need to capture more audio clips 437 | captureAudio(req); 438 | } 439 | } 440 | 441 | public void onImageActivityResult(Request req) { 442 | // create a file object from the image absolute path 443 | JSONObject mediaFile = createMediaFileWithAbsolutePath(this.imageAbsolutePath); 444 | if (mediaFile == null) { 445 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + this.imageAbsolutePath)); 446 | return; 447 | } 448 | 449 | req.results.put(mediaFile); 450 | 451 | if (req.results.length() >= req.limit) { 452 | // Send Uri back to JavaScript for viewing image 453 | pendingRequests.resolveWithSuccess(req); 454 | } else { 455 | // still need to capture more images 456 | captureImage(req); 457 | } 458 | } 459 | 460 | public void onVideoActivityResult(Request req) { 461 | // create a file object from the video absolute path 462 | JSONObject mediaFile = createMediaFileWithAbsolutePath(this.videoAbsolutePath); 463 | if (mediaFile == null) { 464 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + this.videoAbsolutePath)); 465 | return; 466 | } 467 | 468 | req.results.put(mediaFile); 469 | 470 | if (req.results.length() >= req.limit) { 471 | // Send Uri back to JavaScript for viewing video 472 | pendingRequests.resolveWithSuccess(req); 473 | } else { 474 | // still need to capture more video clips 475 | captureVideo(req); 476 | } 477 | } 478 | 479 | /** 480 | * Creates a JSONObject that represents a File from the absolute path 481 | * 482 | * @param path the absolute path saved in FileProvider of the audio/image/video 483 | * @return a JSONObject that represents a File 484 | * @throws IOException 485 | */ 486 | private JSONObject createMediaFileWithAbsolutePath(String path) { 487 | File fp = new File(path); 488 | JSONObject obj = new JSONObject(); 489 | 490 | Class webViewClass = webView.getClass(); 491 | PluginManager pm = null; 492 | try { 493 | Method gpm = webViewClass.getMethod("getPluginManager"); 494 | pm = (PluginManager) gpm.invoke(webView); 495 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 496 | // Do Nothing 497 | } 498 | if (pm == null) { 499 | try { 500 | Field pmf = webViewClass.getField("pluginManager"); 501 | pm = (PluginManager)pmf.get(webView); 502 | } catch (NoSuchFieldException | IllegalAccessException e) { 503 | // Do Nothing 504 | } 505 | } 506 | FileUtils filePlugin = (FileUtils) pm.getPlugin("File"); 507 | LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(fp.getAbsolutePath()); 508 | 509 | try { 510 | // File properties 511 | obj.put("name", fp.getName()); 512 | obj.put("fullPath", Uri.fromFile(fp)); 513 | if (url != null) { 514 | obj.put("localURL", url.toString()); 515 | } 516 | // Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp files 517 | // are reported as video/3gpp. I'm doing this hacky check of the URI to see if it 518 | // is stored in the audio or video content store. 519 | if (fp.getAbsoluteFile().toString().endsWith(".3gp") || fp.getAbsoluteFile().toString().endsWith(".3gpp")) { 520 | Uri data = Uri.fromFile(fp); 521 | if (data.toString().contains("/audio/")) { 522 | obj.put("type", AUDIO_3GPP); 523 | } else { 524 | obj.put("type", VIDEO_3GPP); 525 | } 526 | } else { 527 | obj.put("type", FileHelper.getMimeType(Uri.fromFile(fp), cordova)); 528 | } 529 | 530 | obj.put("lastModifiedDate", fp.lastModified()); 531 | obj.put("size", fp.length()); 532 | } catch (JSONException e) { 533 | // this will never happen 534 | e.printStackTrace(); 535 | } 536 | return obj; 537 | } 538 | 539 | private JSONObject createErrorObject(int code, String message) { 540 | JSONObject obj = new JSONObject(); 541 | try { 542 | obj.put("code", code); 543 | obj.put("message", message); 544 | } catch (JSONException e) { 545 | // This will never happen 546 | } 547 | return obj; 548 | } 549 | 550 | private void executeRequest(Request req) { 551 | switch (req.action) { 552 | case CAPTURE_AUDIO: 553 | this.captureAudio(req); 554 | break; 555 | case CAPTURE_IMAGE: 556 | this.captureImage(req); 557 | break; 558 | case CAPTURE_VIDEO: 559 | this.captureVideo(req); 560 | break; 561 | } 562 | } 563 | 564 | public void onRequestPermissionResult(int requestCode, String[] permissions, 565 | int[] grantResults) throws JSONException { 566 | Request req = pendingRequests.get(requestCode); 567 | 568 | if (req != null) { 569 | boolean success = true; 570 | for(int r:grantResults) { 571 | if (r == PackageManager.PERMISSION_DENIED) { 572 | success = false; 573 | break; 574 | } 575 | } 576 | 577 | if (success) { 578 | executeRequest(req); 579 | } else { 580 | pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_PERMISSION_DENIED, "Permission denied.")); 581 | } 582 | } 583 | } 584 | 585 | public Bundle onSaveInstanceState() { 586 | return pendingRequests.toBundle(); 587 | } 588 | 589 | public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) { 590 | pendingRequests.setLastSavedState(state, callbackContext); 591 | } 592 | 593 | /** 594 | * Gets the ideal buffer size for processing streams of data. 595 | * 596 | * @return The page size of the device. 597 | */ 598 | private int getPageSize() { 599 | // Get the page size of the device. Most devices will be 4096 (4kb) 600 | // Newer devices may be 16kb 601 | long ps = Os.sysconf(OsConstants._SC_PAGE_SIZE); 602 | 603 | // sysconf returns a long because it's a general purpose API 604 | // the expected value of a page size should not exceed an int, 605 | // but we guard it here to avoid integer overflow just in case 606 | if (ps > Integer.MAX_VALUE) { 607 | ps = Integer.MAX_VALUE; 608 | } 609 | 610 | return (int) ps; 611 | } 612 | } 613 | -------------------------------------------------------------------------------- /src/android/FileHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package org.apache.cordova.mediacapture; 20 | 21 | import android.net.Uri; 22 | import android.webkit.MimeTypeMap; 23 | 24 | import org.apache.cordova.CordovaInterface; 25 | 26 | import java.util.Locale; 27 | 28 | // TODO: Replace with CordovaResourceApi.getMimeType() post 3.1. 29 | public class FileHelper { 30 | public static String getMimeTypeForExtension(String path) { 31 | String extension = path; 32 | int lastDot = extension.lastIndexOf('.'); 33 | if (lastDot != -1) { 34 | extension = extension.substring(lastDot + 1); 35 | } 36 | // Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185). 37 | extension = extension.toLowerCase(Locale.getDefault()); 38 | if (extension.equals("3ga")) { 39 | return "audio/3gpp"; 40 | } 41 | return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 42 | } 43 | 44 | /** 45 | * Returns the mime type of the data specified by the given URI string. 46 | * 47 | * @param uriString the URI string of the data 48 | * @return the mime type of the specified data 49 | */ 50 | public static String getMimeType(Uri uri, CordovaInterface cordova) { 51 | String mimeType = null; 52 | if ("content".equals(uri.getScheme())) { 53 | mimeType = cordova.getActivity().getContentResolver().getType(uri); 54 | } else { 55 | mimeType = getMimeTypeForExtension(uri.getPath()); 56 | } 57 | 58 | return mimeType; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/android/FileProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | Unless required by applicable law or agreed to in writing, 11 | software distributed under the License is distributed on an 12 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | KIND, either express or implied. See the License for the 14 | specific language governing permissions and limitations 15 | under the License. 16 | */ 17 | package org.apache.cordova.mediacapture; 18 | 19 | public class FileProvider extends androidx.core.content.FileProvider { 20 | } 21 | -------------------------------------------------------------------------------- /src/android/PendingRequests.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package org.apache.cordova.mediacapture; 21 | 22 | import android.os.Bundle; 23 | import android.util.SparseArray; 24 | 25 | import org.apache.cordova.CallbackContext; 26 | import org.apache.cordova.LOG; 27 | import org.apache.cordova.PluginResult; 28 | import org.json.JSONArray; 29 | import org.json.JSONException; 30 | import org.json.JSONObject; 31 | 32 | /** 33 | * Holds the pending javascript requests for the plugin 34 | */ 35 | public class PendingRequests { 36 | private static final String LOG_TAG = "PendingCaptureRequests"; 37 | 38 | private static final String CURRENT_ID_KEY = "currentReqId"; 39 | private static final String REQUEST_KEY_PREFIX = "request_"; 40 | 41 | private int currentReqId = 0; 42 | private SparseArray requests = new SparseArray(); 43 | 44 | private Bundle lastSavedState; 45 | private CallbackContext resumeContext; 46 | 47 | /** 48 | * Creates a request and adds it to the array of pending requests. Each created request gets a 49 | * unique result code for use with startActivityForResult() and requestPermission() 50 | * @param action The action this request corresponds to (capture image, capture audio, etc.) 51 | * @param options The options for this request passed from the javascript 52 | * @param callbackContext The CallbackContext to return the result to 53 | * @return The newly created Request object with a unique result code 54 | * @throws JSONException 55 | */ 56 | public synchronized Request createRequest(int action, JSONObject options, CallbackContext callbackContext) throws JSONException { 57 | Request req = new Request(action, options, callbackContext); 58 | requests.put(req.requestCode, req); 59 | return req; 60 | } 61 | 62 | /** 63 | * Gets the request corresponding to this request code 64 | * @param requestCode The request code for the desired request 65 | * @return The request corresponding to the given request code or null if such a 66 | * request is not found 67 | */ 68 | public synchronized Request get(int requestCode) { 69 | // Check to see if this request was saved 70 | if (lastSavedState != null && lastSavedState.containsKey(REQUEST_KEY_PREFIX + requestCode)) { 71 | Request r = new Request(lastSavedState.getBundle(REQUEST_KEY_PREFIX + requestCode), this.resumeContext, requestCode); 72 | requests.put(requestCode, r); 73 | 74 | // Only one of the saved requests will get restored, because that's all cordova-android 75 | // supports. Having more than one is an extremely unlikely scenario anyway 76 | this.lastSavedState = null; 77 | this.resumeContext = null; 78 | 79 | return r; 80 | } 81 | 82 | return requests.get(requestCode); 83 | } 84 | 85 | /** 86 | * Removes the request from the array of pending requests and sends an error plugin result 87 | * to the CallbackContext that contains the given error object 88 | * @param req The request to be resolved 89 | * @param error The error to be returned to the CallbackContext 90 | */ 91 | public synchronized void resolveWithFailure(Request req, JSONObject error) { 92 | req.callbackContext.error(error); 93 | requests.remove(req.requestCode); 94 | } 95 | 96 | /** 97 | * Removes the request from the array of pending requests and sends a successful plugin result 98 | * to the CallbackContext that contains the result of the request 99 | * @param req The request to be resolved 100 | */ 101 | public synchronized void resolveWithSuccess(Request req) { 102 | req.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, req.results)); 103 | requests.remove(req.requestCode); 104 | } 105 | 106 | 107 | /** 108 | * Each request gets a unique ID that represents its request code when calls are made to 109 | * Activities and for permission requests 110 | * @return A unique request code 111 | */ 112 | private synchronized int incrementCurrentReqId() { 113 | return currentReqId ++; 114 | } 115 | 116 | /** 117 | * Restore state saved by calling toBundle along with a callbackContext to be used in 118 | * delivering the results of a pending callback 119 | * 120 | * @param lastSavedState The bundle received from toBundle() 121 | * @param resumeContext The callbackContext to return results to 122 | */ 123 | public synchronized void setLastSavedState(Bundle lastSavedState, CallbackContext resumeContext) { 124 | this.lastSavedState = lastSavedState; 125 | this.resumeContext = resumeContext; 126 | this.currentReqId = lastSavedState.getInt(CURRENT_ID_KEY); 127 | } 128 | 129 | /** 130 | * Save the current pending requests to a bundle for saving when the Activity gets destroyed. 131 | * 132 | * @return A Bundle that can be used to restore state using setLastSavedState() 133 | */ 134 | public synchronized Bundle toBundle() { 135 | Bundle bundle = new Bundle(); 136 | bundle.putInt(CURRENT_ID_KEY, currentReqId); 137 | 138 | for (int i = 0; i < requests.size(); i++) { 139 | Request r = requests.valueAt(i); 140 | int requestCode = requests.keyAt(i); 141 | bundle.putBundle(REQUEST_KEY_PREFIX + requestCode, r.toBundle()); 142 | } 143 | 144 | if (requests.size() > 1) { 145 | // This scenario is hopefully very unlikely because there isn't much that can be 146 | // done about it. Should only occur if an external Activity is launched while 147 | // there is a pending permission request and the device is on low memory 148 | LOG.w(LOG_TAG, "More than one media capture request pending on Activity destruction. Some requests will be dropped!"); 149 | } 150 | 151 | return bundle; 152 | } 153 | 154 | /** 155 | * Holds the options and CallbackContext for a capture request made to the plugin. 156 | */ 157 | public class Request { 158 | 159 | // Keys for use in saving requests to a bundle 160 | private static final String ACTION_KEY = "action"; 161 | private static final String LIMIT_KEY = "limit"; 162 | private static final String DURATION_KEY = "duration"; 163 | private static final String QUALITY_KEY = "quality"; 164 | private static final String RESULTS_KEY = "results"; 165 | 166 | // Unique int used to identify this request in any Android Permission or Activity callbacks 167 | public int requestCode; 168 | 169 | // The action that this request is performing 170 | public int action; 171 | 172 | // The number of pics/vids/audio clips to take (CAPTURE_IMAGE, CAPTURE_VIDEO, CAPTURE_AUDIO) 173 | public long limit = 1; 174 | 175 | // Optional max duration of recording in seconds (CAPTURE_VIDEO only) 176 | public int duration = 0; 177 | 178 | // Quality level for video capture 0 low, 1 high (CAPTURE_VIDEO only) 179 | public int quality = 1; 180 | 181 | // The array of results to be returned to the javascript callback on success 182 | public JSONArray results = new JSONArray(); 183 | 184 | // The callback context for this plugin request 185 | private CallbackContext callbackContext; 186 | 187 | private Request(int action, JSONObject options, CallbackContext callbackContext) throws JSONException { 188 | this.callbackContext = callbackContext; 189 | this.action = action; 190 | 191 | if (options != null) { 192 | this.limit = options.optLong("limit", 1); 193 | this.duration = options.optInt("duration", 0); 194 | this.quality = options.optInt("quality", 1); 195 | } 196 | 197 | this.requestCode = incrementCurrentReqId(); 198 | } 199 | 200 | private Request(Bundle bundle, CallbackContext callbackContext, int requestCode) { 201 | this.callbackContext = callbackContext; 202 | this.requestCode = requestCode; 203 | this.action = bundle.getInt(ACTION_KEY); 204 | this.limit = bundle.getLong(LIMIT_KEY); 205 | this.duration = bundle.getInt(DURATION_KEY); 206 | this.quality = bundle.getInt(QUALITY_KEY); 207 | 208 | try { 209 | this.results = new JSONArray(bundle.getString(RESULTS_KEY)); 210 | } catch(JSONException e) { 211 | // This should never be caught 212 | LOG.e(LOG_TAG, "Error parsing results for request from saved bundle", e); 213 | } 214 | } 215 | 216 | private Bundle toBundle() { 217 | Bundle bundle = new Bundle(); 218 | 219 | bundle.putInt(ACTION_KEY, this.action); 220 | bundle.putLong(LIMIT_KEY, this.limit); 221 | bundle.putInt(DURATION_KEY, this.duration); 222 | bundle.putInt(QUALITY_KEY, this.quality); 223 | bundle.putString(RESULTS_KEY, this.results.toString()); 224 | 225 | return bundle; 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/android/res/xml/mediacapture_provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/browser/CaptureProxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | const MediaFile = require('cordova-plugin-media-capture.MediaFile'); 23 | const MediaFileData = require('cordova-plugin-media-capture.MediaFileData'); 24 | const CaptureError = require('cordova-plugin-media-capture.CaptureError'); 25 | 26 | /** 27 | * Helper function that converts data URI to Blob 28 | * @param {String} dataURI Data URI to convert 29 | * @return {Blob} Blob, covnerted from DataURI String 30 | */ 31 | function dataURItoBlob (dataURI) { 32 | // convert base64 to raw binary data held in a string 33 | // doesn't handle URLEncoded DataURIs 34 | const byteString = atob(dataURI.split(',')[1]); // eslint-disable-line no-undef 35 | 36 | // separate out the mime component 37 | const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 38 | 39 | // write the bytes of the string to an ArrayBuffer 40 | const ab = new ArrayBuffer(byteString.length); 41 | const ia = new Uint8Array(ab); 42 | for (let i = 0; i < byteString.length; i++) { 43 | ia[i] = byteString.charCodeAt(i); 44 | } 45 | 46 | // write the ArrayBuffer to a blob, and you're done 47 | return new Blob([ab], { type: mimeString }); // eslint-disable-line no-undef 48 | } 49 | 50 | /** 51 | * Creates basic camera UI with preview 'video' element and 'Cancel' button 52 | * Capture starts, when you clicking on preview. 53 | */ 54 | function CameraUI () { 55 | // Root element for preview 56 | const container = document.createElement('div'); 57 | container.style.cssText = 58 | 'left: 0px; top: 0px; width: 100%; height: 100%; position: fixed; z-index:9999;' + 59 | 'padding: 40px; background-color: rgba(0,0,0,0.75);' + 60 | 'text-align:center; visibility: hidden'; 61 | 62 | // Set up root element contetnts 63 | container.innerHTML = 64 | '
' + 65 | '

' + 66 | 'Click on preview to capture image. Click outside of preview to cancel.

' + 67 | '' + 68 | '
'; 69 | 70 | // Add container element to DOM but do not display it since visibility == hidden 71 | document.body.appendChild(container); 72 | 73 | // Create fullscreen preview 74 | const preview = document.getElementById('capturePreview'); 75 | preview.autoplay = true; 76 | // We'll show preview only when video element content 77 | // is fully loaded to avoid glitches 78 | preview.onplay = function () { 79 | container.style.visibility = 'visible'; 80 | }; 81 | 82 | this.container = container; 83 | this.preview = preview; 84 | } 85 | 86 | /** 87 | * Displays capture preview 88 | * @param {Number} count Number of images to take 89 | * @param {Function} successCB Success callback, that accepts data URL of captured image 90 | * @param {Function} errorCB Error callback 91 | */ 92 | CameraUI.prototype.startPreview = function (count, successCB, errorCB) { 93 | const that = this; 94 | 95 | this.preview.onclick = function (e) { 96 | // proceed with capture here 97 | // We don't need to propagate click event to parent elements. 98 | // Otherwise click on vieo element will trigger click event handler 99 | // for preview root element and cause preview cancellation 100 | e.stopPropagation(); 101 | // Create canvas element, put video frame on it 102 | // and save its contant as Data URL 103 | const canvas = document.createElement('canvas'); 104 | canvas.width = this.videoWidth; 105 | canvas.height = this.videoHeight; 106 | canvas.getContext('2d').drawImage(that.preview, 0, 0); 107 | successCB(canvas.toDataURL('image/jpeg')); 108 | }; 109 | 110 | this.container.onclick = function () { 111 | // Cancel capture here 112 | errorCB(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); 113 | }; 114 | 115 | navigator.getUserMedia( 116 | { video: true }, 117 | function (previewStream) { 118 | // Save video stream to be able to stop it later 119 | that._previewStream = previewStream; 120 | that.preview.src = URL.createObjectURL(previewStream); // eslint-disable-line no-undef 121 | // We don't need to set visibility = true for preview element 122 | // since this will be done automatically in onplay event handler 123 | }, 124 | function (/* err */) { 125 | errorCB(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR)); 126 | } 127 | ); 128 | }; 129 | 130 | /** 131 | * Destroys camera preview, removes all elements created 132 | */ 133 | CameraUI.prototype.destroyPreview = function () { 134 | this.preview.pause(); 135 | this.preview.src = null; 136 | this._previewStream.stop(); 137 | this._previewStream = null; 138 | if (this.container) { 139 | document.body.removeChild(this.container); 140 | } 141 | }; 142 | 143 | module.exports = { 144 | captureAudio: function (successCallback, errorCallback) { 145 | if (errorCallback) { 146 | errorCallback(new CaptureError(CaptureError.CAPTURE_NOT_SUPPORTED)); 147 | } 148 | }, 149 | 150 | captureVideo: function (successCallback, errorCallback) { 151 | if (errorCallback) { 152 | errorCallback(new CaptureError(CaptureError.CAPTURE_NOT_SUPPORTED)); 153 | } 154 | }, 155 | 156 | captureImage: function (successCallback, errorCallback, args) { 157 | const fail = function (code) { 158 | if (errorCallback) { 159 | errorCallback(new CaptureError(code || CaptureError.CAPTURE_INTERNAL_ERR)); 160 | } 161 | }; 162 | 163 | const options = args[0]; 164 | 165 | const limit = options.limit || 1; 166 | if (typeof limit !== 'number' || limit < 1) { 167 | fail(CaptureError.CAPTURE_INVALID_ARGUMENT); 168 | return; 169 | } 170 | 171 | // Counter for already taken images 172 | let imagesTaken = 0; 173 | 174 | navigator.getUserMedia = 175 | navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; 176 | 177 | if (!navigator.getUserMedia) { 178 | fail(CaptureError.CAPTURE_NOT_SUPPORTED); 179 | return; 180 | } 181 | 182 | const ui = new CameraUI(); 183 | ui.startPreview( 184 | limit, 185 | function (data) { 186 | // Check if we're done with capture. If so, then destroy UI 187 | if (++imagesTaken >= limit) { 188 | ui.destroyPreview(); 189 | } 190 | 191 | // Array of resultant MediaFiles 192 | const mediaFiles = []; 193 | 194 | // save data to file here 195 | window.requestFileSystem( 196 | window.TEMPORARY, 197 | data.length * limit, 198 | function (fileSystem) { 199 | // If we need to capture multiple files, then append counter to filename 200 | const fileName = limit <= 1 ? 'image.jpg' : 'image' + imagesTaken + '.jpg'; 201 | fileSystem.root.getFile( 202 | fileName, 203 | { create: true }, 204 | function (file) { 205 | file.createWriter(function (writer) { 206 | writer.onwriteend = function () { 207 | file.getMetadata(function (meta) { 208 | mediaFiles.push( 209 | new MediaFile(file.name, file.toURL(), 'image/jpeg', meta.modificationTime, meta.size) 210 | ); 211 | // Check if we're done with capture. If so, then call a successCallback 212 | if (imagesTaken >= limit) { 213 | successCallback(mediaFiles); 214 | } 215 | }, fail); 216 | }; 217 | writer.onerror = fail; 218 | // Since success callback for start preview returns 219 | // a base64 encoded string, we need to convert it to blob first 220 | writer.write(dataURItoBlob(data)); 221 | }); 222 | }, 223 | fail 224 | ); 225 | }, 226 | fail 227 | ); 228 | }, 229 | function (err) { 230 | ui.destroyPreview(); 231 | fail(err.code); 232 | } 233 | ); 234 | }, 235 | 236 | getFormatData: function (successCallback, errorCallback, args) { 237 | const img = document.createElement('img'); 238 | img.src = args[0]; 239 | img.onload = function () { 240 | if (successCallback) { 241 | successCallback(new MediaFileData(null, 0, img.height, img.width, 0)); 242 | } 243 | }; 244 | } 245 | }; 246 | 247 | require('cordova/exec/proxy').add('Capture', module.exports); 248 | -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/controls_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/controls_bg.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/controls_bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/controls_bg@2x.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/controls_bg@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/controls_bg@2x~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/controls_bg~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/controls_bg~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // controller title for Videos 21 | "Videos title" = "Videos"; 22 | // accessibility label for recording button 23 | "toggle audio recording" = "starten/beenden der Tonaufnahme"; 24 | // notification spoken by VoiceOver when timed recording finishes 25 | "timed recording complete" = "programmierte Aufnahme beendet"; 26 | // accessibility hint for display of recorded elapsed time 27 | "recorded time in minutes and seconds" = "aufgenommene Zeit in Minuten und Sekunden"; 28 | // Access denied 29 | "Access denied" = "Zugriff abgelehnt"; 30 | // Microphone access has been prohibited 31 | "Access to the microphone has been prohibited. Please enable it in the Settings app to continue." = "Der Zugriff auf das Mikrofon wurde verboten. Bitte aktivieren Sie es in der Einstellungen-App, um fortzufahren."; 32 | -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // controller title for Videos 21 | "Videos title" = "Videos"; 22 | // accessibility label for recording button 23 | "toggle audio recording" = "toggle audio recording"; 24 | // notification spoken by VoiceOver when timed recording finishes 25 | "timed recording complete" = "timed recording complete"; 26 | // accessibility hint for display of recorded elapsed time 27 | "recorded time in minutes and seconds" = "recorded time in minutes and seconds"; 28 | // Access denied 29 | "Access denied" = "Access denied"; 30 | // Microphone access has been prohibited 31 | "Access to the microphone has been prohibited. Please enable it in the Settings app to continue." = "Access to the microphone has been prohibited. Please enable it in the Settings app to continue."; 32 | -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // controller title for Videos 21 | "Videos title" = "Videos"; 22 | // accessibility label for recording button 23 | "toggle audio recording" = "activar/desactivar la grabación de audio"; 24 | // notification spoken by VoiceOver when timed recording finishes 25 | "timed recording complete" = "límite de grabación alcanzado"; 26 | // accessibility hint for display of recorded elapsed time 27 | "recorded time in minutes and seconds" = "tiempo de grabación en minutos y segundos"; 28 | // Access denied 29 | "Access denied" = "Acceso denegado"; 30 | // Microphone access has been prohibited 31 | "Access to the microphone has been prohibited. Please enable it in the Settings app to continue." = "Se ha prohibido el acceso al micrófono. Habilítelo en la aplicación Configuración para continuar."; 32 | -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // controller title for Videos 21 | "Videos title" = "Vidéos"; 22 | // accessibility label for recording button 23 | "toggle audio recording" = "activer/désactiver l'enregistrement audio"; 24 | // notification spoken by VoiceOver when timed recording finishes 25 | "timed recording complete" = "limite d'enregistrement atteinte"; 26 | // accessibility hint for display of recorded elapsed time 27 | "recorded time in minutes and seconds" = "temps d'enregistrement en minutes et secondes"; 28 | // Access denied 29 | "Access denied" = "Accès refusé"; 30 | // Microphone access has been prohibited 31 | "Access to the microphone has been prohibited. Please enable it in the Settings app to continue." = "L'accès au micro a été interdit. Veuillez l'activer dans l'application Paramètres pour continuer."; 32 | -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/microphone-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/microphone-568h@2x~iphone.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/microphone.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/microphone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/microphone@2x.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/microphone@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/microphone@2x~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/microphone~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/microphone~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/record_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/record_button.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/record_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/record_button@2x.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/record_button@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/record_button@2x~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/record_button~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/record_button~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/recording_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/recording_bg.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/recording_bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/recording_bg@2x.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/recording_bg@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/recording_bg@2x~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/recording_bg~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/recording_bg~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/se.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | // controller title for Videos 21 | "Videos title" = "Videor"; 22 | // accessibility label for recording button 23 | "toggle audio recording" = "börja/avsluta inspelning"; 24 | // notification spoken by VoiceOver when timed recording finishes 25 | "timed recording complete" = "inspelning har avslutad"; 26 | // accessibility hint for display of recorded elapsed time 27 | "recorded time in minutes and seconds" = "inspelad tid in minuter och sekund"; 28 | // Access denied 29 | "Access denied" = "Tillträde beviljas ej"; 30 | // Microphone access has been prohibited 31 | "Access to the microphone has been prohibited. Please enable it in the Settings app to continue." = "Tillgång till mikrofonen har förbjudits. Aktivera det i appen Inställningar för att fortsätta."; 32 | -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/stop_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/stop_button.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/stop_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/stop_button@2x.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/stop_button@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/stop_button@2x~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.bundle/stop_button~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/cordova-plugin-media-capture/00b33f44991cd86d787be36ba12063f717d2c683/src/ios/CDVCapture.bundle/stop_button~ipad.png -------------------------------------------------------------------------------- /src/ios/CDVCapture.h: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import "CDVFile.h" 25 | 26 | enum CDVCaptureError { 27 | CAPTURE_INTERNAL_ERR = 0, 28 | CAPTURE_APPLICATION_BUSY = 1, 29 | CAPTURE_INVALID_ARGUMENT = 2, 30 | CAPTURE_NO_MEDIA_FILES = 3, 31 | CAPTURE_PERMISSION_DENIED = 4, 32 | CAPTURE_NOT_SUPPORTED = 20 33 | }; 34 | typedef NSUInteger CDVCaptureError; 35 | 36 | @interface CDVImagePicker : UIImagePickerController 37 | { 38 | NSString* callbackid; 39 | NSInteger quality; 40 | NSString* mimeType; 41 | } 42 | @property (assign) NSInteger quality; 43 | @property (copy) NSString* callbackId; 44 | @property (copy) NSString* mimeType; 45 | 46 | @end 47 | 48 | @interface CDVCapture : CDVPlugin 49 | { 50 | CDVImagePicker* pickerController; 51 | BOOL inUse; 52 | } 53 | @property BOOL inUse; 54 | - (void)captureAudio:(CDVInvokedUrlCommand*)command; 55 | - (void)captureImage:(CDVInvokedUrlCommand*)command; 56 | - (CDVPluginResult*)processImage:(UIImage*)image type:(NSString*)mimeType forCallbackId:(NSString*)callbackId; 57 | - (void)captureVideo:(CDVInvokedUrlCommand*)command; 58 | - (CDVPluginResult*)processVideo:(NSString*)moviePath forCallbackId:(NSString*)callbackId; 59 | - (void)getMediaModes:(CDVInvokedUrlCommand*)command; 60 | - (void)getFormatData:(CDVInvokedUrlCommand*)command; 61 | - (NSDictionary*)getMediaDictionaryFromPath:(NSString*)fullPath ofType:(NSString*)type; 62 | - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info; 63 | - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo; 64 | - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker; 65 | 66 | @end 67 | 68 | @interface CDVAudioNavigationController : UINavigationController 69 | 70 | @end 71 | 72 | /* AudioRecorderViewController is used to create a simple view for audio recording. 73 | * It is created from [Capture captureAudio]. It creates a very simple interface for 74 | * recording by presenting just a record/stop button and a Done button to close the view. 75 | * The recording time is displayed and recording for a specified duration is supported. When duration 76 | * is specified there is no UI to the user - recording just stops when the specified 77 | * duration is reached. The UI has been minimized to avoid localization. 78 | */ 79 | @interface CDVAudioRecorderViewController : UIViewController 80 | { 81 | CDVCaptureError errorCode; 82 | NSString* callbackId; 83 | NSNumber* duration; 84 | CDVCapture* captureCommand; 85 | UIBarButtonItem* doneButton; 86 | UIView* recordingView; 87 | UIButton* recordButton; 88 | UIImage* recordImage; 89 | UIImage* stopRecordImage; 90 | UILabel* timerLabel; 91 | AVAudioRecorder* avRecorder; 92 | AVAudioSession* avSession; 93 | CDVPluginResult* pluginResult; 94 | NSTimer* timer; 95 | BOOL isTimed; 96 | } 97 | @property (nonatomic) CDVCaptureError errorCode; 98 | @property (nonatomic, copy) NSString* callbackId; 99 | @property (nonatomic, copy) NSNumber* duration; 100 | @property (nonatomic, strong) CDVCapture* captureCommand; 101 | @property (nonatomic, strong) UIBarButtonItem* doneButton; 102 | @property (nonatomic, strong) UIView* recordingView; 103 | @property (nonatomic, strong) UIButton* recordButton; 104 | @property (nonatomic, strong) UIImage* recordImage; 105 | @property (nonatomic, strong) UIImage* stopRecordImage; 106 | @property (nonatomic, strong) UILabel* timerLabel; 107 | @property (nonatomic, strong) AVAudioRecorder* avRecorder; 108 | @property (nonatomic, strong) AVAudioSession* avSession; 109 | @property (nonatomic, strong) CDVPluginResult* pluginResult; 110 | @property (nonatomic, strong) NSTimer* timer; 111 | @property (nonatomic) BOOL isTimed; 112 | 113 | - (id)initWithCommand:(CDVPlugin*)theCommand duration:(NSNumber*)theDuration callbackId:(NSString*)theCallbackId; 114 | - (void)processButton:(id)sender; 115 | - (void)stopRecordingCleanup; 116 | - (void)dismissAudioView:(id)sender; 117 | - (NSString*)formatTime:(int)interval; 118 | - (void)updateTime; 119 | @end 120 | -------------------------------------------------------------------------------- /src/windows/MediaFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /* global Windows */ 23 | 24 | const MediaFileData = require('cordova-plugin-media-capture.MediaFileData'); 25 | const CaptureError = require('cordova-plugin-media-capture.CaptureError'); 26 | 27 | module.exports = { 28 | getFormatData: function (successCallback, errorCallback, args) { 29 | Windows.Storage.StorageFile.getFileFromPathAsync(this.fullPath).then( 30 | function (storageFile) { 31 | const mediaTypeFlag = String(storageFile.contentType).split('/')[0].toLowerCase(); 32 | if (mediaTypeFlag === 'audio') { 33 | storageFile.properties.getMusicPropertiesAsync().then( 34 | function (audioProperties) { 35 | successCallback(new MediaFileData(null, audioProperties.bitrate, 0, 0, audioProperties.duration / 1000)); 36 | }, 37 | function () { 38 | errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); 39 | } 40 | ); 41 | } else if (mediaTypeFlag === 'video') { 42 | storageFile.properties.getVideoPropertiesAsync().then( 43 | function (videoProperties) { 44 | successCallback( 45 | new MediaFileData( 46 | null, 47 | videoProperties.bitrate, 48 | videoProperties.height, 49 | videoProperties.width, 50 | videoProperties.duration / 1000 51 | ) 52 | ); 53 | }, 54 | function () { 55 | errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); 56 | } 57 | ); 58 | } else if (mediaTypeFlag === 'image') { 59 | storageFile.properties.getImagePropertiesAsync().then( 60 | function (imageProperties) { 61 | successCallback(new MediaFileData(null, 0, imageProperties.height, imageProperties.width, 0)); 62 | }, 63 | function () { 64 | errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); 65 | } 66 | ); 67 | } else { 68 | errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); 69 | } 70 | }, 71 | function () { 72 | errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); 73 | } 74 | ); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-media-capture-tests", 3 | "version": "6.0.1-dev", 4 | "description": "", 5 | "cordova": { 6 | "id": "cordova-plugin-media-capture-tests", 7 | "platforms": [] 8 | }, 9 | "keywords": [ 10 | "ecosystem:cordova" 11 | ], 12 | "author": "", 13 | "license": "Apache 2.0" 14 | } 15 | -------------------------------------------------------------------------------- /tests/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 26 | Cordova Media Capture Plugin Tests 27 | Apache 2.0 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /* global CaptureAudioOptions, CaptureImageOptions, CaptureVideoOptions, CaptureError */ 23 | /* global Media, MediaFile, MediaFileData, resolveLocalFileSystemURL, cordova */ 24 | 25 | exports.defineAutoTests = function () { 26 | describe('Capture (navigator.device.capture)', function () { 27 | it('capture.spec.1 should exist', function () { 28 | expect(navigator.device).toBeDefined(); 29 | expect(navigator.device.capture).toBeDefined(); 30 | }); 31 | 32 | it('capture.spec.2 should have the correct properties ', function () { 33 | expect(navigator.device.capture.supportedAudioModes).toBeDefined(); 34 | expect(navigator.device.capture.supportedImageModes).toBeDefined(); 35 | expect(navigator.device.capture.supportedVideoModes).toBeDefined(); 36 | }); 37 | 38 | it('capture.spec.3 should contain a captureAudio function', function () { 39 | expect(navigator.device.capture.captureAudio).toBeDefined(); 40 | expect(typeof navigator.device.capture.captureAudio === 'function').toBe(true); 41 | }); 42 | 43 | it('capture.spec.4 should contain a captureImage function', function () { 44 | expect(navigator.device.capture.captureImage).toBeDefined(); 45 | expect(typeof navigator.device.capture.captureImage === 'function').toBe(true); 46 | }); 47 | 48 | it('capture.spec.5 should contain a captureVideo function', function () { 49 | expect(navigator.device.capture.captureVideo).toBeDefined(); 50 | expect(typeof navigator.device.capture.captureVideo === 'function').toBe(true); 51 | }); 52 | 53 | describe('CaptureAudioOptions', function () { 54 | it('capture.spec.6 CaptureAudioOptions constructor should exist', function () { 55 | const options = new CaptureAudioOptions(); 56 | expect(options).toBeDefined(); 57 | expect(options.limit).toBeDefined(); 58 | expect(options.duration).toBeDefined(); 59 | }); 60 | }); 61 | 62 | describe('CaptureImageOptions', function () { 63 | it('capture.spec.7 CaptureImageOptions constructor should exist', function () { 64 | const options = new CaptureImageOptions(); 65 | expect(options).toBeDefined(); 66 | expect(options.limit).toBeDefined(); 67 | }); 68 | }); 69 | 70 | describe('CaptureVideoOptions', function () { 71 | it('capture.spec.8 CaptureVideoOptions constructor should exist', function () { 72 | const options = new CaptureVideoOptions(); 73 | expect(options).toBeDefined(); 74 | expect(options.limit).toBeDefined(); 75 | expect(options.duration).toBeDefined(); 76 | }); 77 | }); 78 | 79 | describe('CaptureError interface', function () { 80 | it('capture.spec.9 CaptureError constants should be defined', function () { 81 | expect(CaptureError.CAPTURE_INTERNAL_ERR).toBe(0); 82 | expect(CaptureError.CAPTURE_APPLICATION_BUSY).toBe(1); 83 | expect(CaptureError.CAPTURE_INVALID_ARGUMENT).toBe(2); 84 | expect(CaptureError.CAPTURE_NO_MEDIA_FILES).toBe(3); 85 | }); 86 | 87 | it('capture.spec.10 CaptureError properties should exist', function () { 88 | const error = new CaptureError(); 89 | expect(error).toBeDefined(); 90 | expect(error.code).toBeDefined(); 91 | }); 92 | }); 93 | 94 | describe('MediaFileData', function () { 95 | it('capture.spec.11 MediaFileData constructor should exist', function () { 96 | const fileData = new MediaFileData(); 97 | expect(fileData).toBeDefined(); 98 | expect(fileData.bitrate).toBeDefined(); 99 | expect(fileData.codecs).toBeDefined(); 100 | expect(fileData.duration).toBeDefined(); 101 | expect(fileData.height).toBeDefined(); 102 | expect(fileData.width).toBeDefined(); 103 | }); 104 | }); 105 | 106 | describe('MediaFile', function () { 107 | it('capture.spec.12 MediaFile constructor should exist', function () { 108 | const fileData = new MediaFile(); 109 | expect(fileData).toBeDefined(); 110 | expect(fileData.name).toBeDefined(); 111 | expect(fileData.type).toBeDefined(); 112 | expect(fileData.lastModifiedDate).toBeDefined(); 113 | expect(fileData.size).toBeDefined(); 114 | }); 115 | }); 116 | }); 117 | }; 118 | 119 | /******************************************************************************/ 120 | /******************************************************************************/ 121 | /******************************************************************************/ 122 | 123 | exports.defineManualTests = function (contentEl, createActionButton) { 124 | const pageStartTime = +new Date(); 125 | 126 | function log (value) { 127 | document.getElementById('camera_status').textContent += (new Date() - pageStartTime) / 1000 + ': ' + value + '\n'; 128 | console.log(value); 129 | } 130 | 131 | function captureAudioWin (mediaFiles) { 132 | const path = mediaFiles[0].fullPath; 133 | log('Audio captured: ' + path); 134 | const m = new Media(path); 135 | m.play(); 136 | getFileMetadata(mediaFiles[0]); 137 | } 138 | 139 | function captureAudioFail (e) { 140 | log('Error getting audio: ' + e.code); 141 | } 142 | 143 | function getAudio () { 144 | clearStatus(); 145 | const options = { limit: 1, duration: 10 }; 146 | navigator.device.capture.captureAudio(captureAudioWin, captureAudioFail, options); 147 | } 148 | 149 | function captureImageWin (mediaFiles) { 150 | let path = mediaFiles[0].fullPath; 151 | // Necessary since windows doesn't allow file URLs for elements 152 | if (cordova.platformId === 'windows' || cordova.platformId === 'windows8' || cordova.platformId === 'browser') { 153 | // eslint-disable-line no-undef 154 | path = mediaFiles[0].localURL; 155 | } 156 | log('Image captured: ' + path); 157 | document.getElementById('camera_image').src = path; 158 | } 159 | 160 | function captureImagesWin (mediaFiles) { 161 | let path = mediaFiles[0].fullPath; 162 | // Necessary since windows doesn't allow file URLs for elements 163 | if (cordova.platformId === 'windows' || cordova.platformId === 'windows8' || cordova.platformId === 'browser') { 164 | // eslint-disable-line no-undef 165 | path = mediaFiles[0].localURL; 166 | } 167 | const path2 = mediaFiles[1].fullPath; 168 | // Necessary since windows doesn't allow file URLs for elements 169 | if (cordova.platformId === 'windows' || cordova.platformId === 'windows8' || cordova.platformId === 'browser') { 170 | // eslint-disable-line no-undef 171 | path = mediaFiles[1].localURL; 172 | } 173 | const path3 = mediaFiles[2].fullPath; 174 | // Necessary since windows doesn't allow file URLs for elements 175 | if (cordova.platformId === 'windows' || cordova.platformId === 'windows8' || cordova.platformId === 'browser') { 176 | // eslint-disable-line no-undef 177 | path = mediaFiles[2].localURL; 178 | } 179 | log('Image captured: ' + path); 180 | log('Image captured: ' + path2); 181 | log('Image captured: ' + path3); 182 | document.getElementById('camera_image').src = path; 183 | document.getElementById('camera_image2').src = path2; 184 | document.getElementById('camera_image3').src = path3; 185 | } 186 | 187 | function captureImageFail (e) { 188 | log('Error getting image: ' + e.code); 189 | } 190 | 191 | function getImage () { 192 | clearStatus(); 193 | const options = { limit: 1 }; 194 | navigator.device.capture.captureImage(captureImageWin, captureImageFail, options); 195 | } 196 | 197 | function getImages () { 198 | clearStatus(); 199 | const options = { limit: 3 }; 200 | navigator.device.capture.captureImage(captureImagesWin, captureImageFail, options); 201 | } 202 | 203 | function captureVideoWin (mediaFiles) { 204 | const path = mediaFiles[0].fullPath; 205 | log('Video captured: ' + path); 206 | 207 | // need to inject the video element into the html 208 | // doesn't seem to work if you have a pre-existing video element and 209 | // add in a source tag 210 | const vid = document.createElement('video'); 211 | vid.id = 'theVideo'; 212 | vid.width = '320'; 213 | vid.height = '240'; 214 | vid.controls = 'true'; 215 | const source_vid = document.createElement('source'); 216 | source_vid.id = 'theSource'; 217 | source_vid.src = path; 218 | vid.appendChild(source_vid); 219 | document.getElementById('video_container').appendChild(vid); 220 | getFileMetadata(mediaFiles[0]); 221 | } 222 | 223 | function getFileMetadata (mediaFile) { 224 | mediaFile.getFormatData(getMetadataWin, getMetadataFail); 225 | } 226 | 227 | function getMetadataWin (metadata) { 228 | const strMetadata = 'duration = ' + metadata.duration + '\n' + 'width = ' + metadata.width + '\n' + 'height = ' + metadata.height; 229 | log(strMetadata); 230 | } 231 | 232 | function getMetadataFail (e) { 233 | log('Error getting metadata: ' + e.code); 234 | } 235 | 236 | function captureVideoFail (e) { 237 | log('Error getting video: ' + e.code); 238 | } 239 | 240 | function getVideo () { 241 | clearStatus(); 242 | const options = { limit: 1, duration: 10 }; 243 | navigator.device.capture.captureVideo(captureVideoWin, captureVideoFail, options); 244 | } 245 | 246 | function permissionWasNotAllowed () { 247 | log('Media has been captured. Have you forgotten to disallow camera for this app?'); 248 | } 249 | 250 | function catchPermissionError (error) { 251 | if (CaptureError.CAPTURE_PERMISSION_DENIED === error.code) { 252 | log('Sucess: permission error has been detected!'); 253 | } else { 254 | log('Error: another error with code: ' + error.code); 255 | } 256 | } 257 | 258 | function getVideoPermissionError () { 259 | const options = { limit: 1, duration: 10 }; 260 | navigator.device.capture.captureVideo(permissionWasNotAllowed, catchPermissionError, options); 261 | } 262 | 263 | function getImagePermissionError () { 264 | const options = { limit: 1 }; 265 | navigator.device.capture.captureImage(permissionWasNotAllowed, catchPermissionError, options); 266 | } 267 | 268 | function resolveMediaFileURL (mediaFile, callback) { 269 | resolveLocalFileSystemURL( 270 | mediaFile.localURL, 271 | function (entry) { 272 | log('Resolved by URL: ' + mediaFile.localURL); 273 | if (callback) callback(); 274 | }, 275 | function (err) { 276 | log('Failed to resolve by URL: ' + mediaFile.localURL); 277 | log('Error: ' + JSON.stringify(err)); 278 | if (callback) callback(); 279 | } 280 | ); 281 | } 282 | 283 | function resolveMediaFile (mediaFile, callback) { 284 | resolveLocalFileSystemURL( 285 | mediaFile.fullPath, 286 | function (entry) { 287 | log('Resolved by path: ' + mediaFile.fullPath); 288 | if (callback) callback(); 289 | }, 290 | function (err) { 291 | log('Failed to resolve by path: ' + mediaFile.fullPath); 292 | log('Error: ' + JSON.stringify(err)); 293 | if (callback) callback(); 294 | } 295 | ); 296 | } 297 | 298 | function resolveVideo () { 299 | clearStatus(); 300 | const options = { limit: 1, duration: 5 }; 301 | navigator.device.capture.captureVideo( 302 | function (mediaFiles) { 303 | captureVideoWin(mediaFiles); 304 | resolveMediaFile(mediaFiles[0], function () { 305 | resolveMediaFileURL(mediaFiles[0]); 306 | }); 307 | }, 308 | captureVideoFail, 309 | options 310 | ); 311 | } 312 | 313 | function clearStatus () { 314 | document.getElementById('camera_status').innerHTML = ''; 315 | document.getElementById('camera_image').src = 'about:blank'; 316 | document.getElementById('camera_image2').src = 'about:blank'; 317 | document.getElementById('camera_image3').src = 'about:blank'; 318 | } 319 | 320 | /******************************************************************************/ 321 | 322 | contentEl.innerHTML = 323 | '
' + 324 | 'Status:
' + 325 | 'img1: ' + 326 | 'img2: ' + 327 | 'img3: ' + 328 | 'video:
' + 329 | '
' + 330 | 'Expected result: Audio recorder will come up. Press record button to record for 10 seconds. Press Done. Status box will update with audio file and automatically play recording.' + 331 | '

' + 332 | 'Expected result: Status box will update with image just taken.' + 333 | '

' + 334 | 'Expected result: Status box will update with images just taken.' + 335 | '

' + 336 | 'Expected result: Record 10 second video. Status box will update with video file that you can play.' + 337 | '

' + 338 | 'Expected result: Record 5 second video. Status box will show that URL was resolved and video will get added at the bottom of the status box for playback.' + 339 | '

' + 340 | 'Expected result (iOS only): camera picker and alert with message that camera access is prohibited are shown. The alert has 2 buttons: OK and Settings. By click on "OK" camera is hidden, by pressing Settings it shows privacy settings for the app' + 341 | '

' + 342 | 'Expected result (iOS only): camera picker and alert with message that camera access is prohibited are shown. The alert has 2 buttons: OK and Settings. By click on "OK" camera is hidden, by pressing Settings it shows privacy settings for the app'; 343 | 344 | createActionButton( 345 | 'Capture 10 sec of audio and play', 346 | function () { 347 | getAudio(); 348 | }, 349 | 'audio' 350 | ); 351 | 352 | createActionButton( 353 | 'Capture 1 image', 354 | function () { 355 | getImage(); 356 | }, 357 | 'image' 358 | ); 359 | 360 | createActionButton( 361 | 'Capture 3 images', 362 | function () { 363 | getImages(); 364 | }, 365 | 'images' 366 | ); 367 | 368 | createActionButton( 369 | 'Capture 10 sec of video', 370 | function () { 371 | getVideo(); 372 | }, 373 | 'video' 374 | ); 375 | 376 | createActionButton( 377 | 'Capture 5 sec of video and resolve', 378 | function () { 379 | resolveVideo(); 380 | }, 381 | 'video_and_resolve' 382 | ); 383 | 384 | createActionButton( 385 | 'Disable access to Camera and click to capture video', 386 | function () { 387 | getVideoPermissionError(); 388 | }, 389 | 'prohibited_camera_video' 390 | ); 391 | 392 | createActionButton( 393 | 'Disable access to Camera and click to capture image', 394 | function () { 395 | getImagePermissionError(); 396 | }, 397 | 'prohibited_camera_image' 398 | ); 399 | }; 400 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Apache Cordova MediaCapture plugin 2 | // Project: https://github.com/apache/cordova-plugin-media-capture 3 | // Definitions by: Microsoft Open Technologies Inc 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | // 6 | // Copyright (c) Microsoft Open Technologies Inc 7 | // Licensed under the MIT license 8 | 9 | interface Navigator { 10 | device: Device; 11 | } 12 | 13 | interface Device { 14 | capture: Capture; 15 | } 16 | 17 | /** This plugin provides access to the device's audio, image, and video capture capabilities. */ 18 | interface Capture { 19 | /** 20 | * Start the audio recorder application and return information about captured audio clip files. 21 | * @param onSuccess Executes when the capture operation finishes with an array 22 | * of MediaFile objects describing each captured audio clip file. 23 | * @param onError Executes, if the user terminates the operation before an audio clip is captured, 24 | * with a CaptureError object, featuring the CaptureError.CAPTURE_NO_MEDIA_FILES error code. 25 | * @param options Encapsulates audio capture configuration options. 26 | */ 27 | captureAudio( 28 | onSuccess: (mediaFiles: MediaFile[]) => void, 29 | onError: (error: CaptureError) => void, 30 | options?: AudioOptions): void ; 31 | /** 32 | * Start the camera application and return information about captured image files. 33 | * @param onSuccess Executes when the capture operation finishes with an array 34 | * of MediaFile objects describing each captured image clip file. 35 | * @param onError Executes, if the user terminates the operation before an audio clip is captured, 36 | * with a CaptureError object, featuring the CaptureError.CAPTURE_NO_MEDIA_FILES error code. 37 | * @param options Encapsulates audio capture configuration options. 38 | */ 39 | captureImage( 40 | onSuccess: (mediaFiles: MediaFile[]) => void, 41 | onError: (error: CaptureError) => void, 42 | options?: ImageOptions): void ; 43 | /** 44 | * Start the video recorder application and return information about captured video clip files. 45 | * @param onSuccess Executes when the capture operation finishes with an array 46 | * of MediaFile objects describing each captured video clip file. 47 | * @param onError Executes, if the user terminates the operation before an audio clip is captured, 48 | * with a CaptureError object, featuring the CaptureError.CAPTURE_NO_MEDIA_FILES error code. 49 | * @param options Encapsulates audio capture configuration options. 50 | */ 51 | captureVideo( 52 | onSuccess: (mediaFiles: MediaFile[]) => void, 53 | onError: (error: CaptureError) => void, 54 | options?: VideoOptions): void ; 55 | /** The audio recording formats supported by the device. */ 56 | supportedAudioModes: ConfigurationData[]; 57 | /** The recording image sizes and formats supported by the device. */ 58 | supportedImageModes: ConfigurationData[]; 59 | /** The recording video resolutions and formats supported by the device. */ 60 | supportedVideoModes: ConfigurationData[]; 61 | } 62 | 63 | /** Encapsulates properties of a media capture file. */ 64 | interface MediaFile { 65 | /** The name of the file, without path information. */ 66 | name: string; 67 | /** The full path of the file, including the name. */ 68 | fullPath: string; 69 | /** The file's mime type */ 70 | type: string; 71 | /** The date and time when the file was last modified. */ 72 | lastModifiedDate: Date; 73 | /** The size of the file, in bytes. */ 74 | size: number; 75 | /** 76 | * Retrieves format information about the media capture file. 77 | * @param successCallback Invoked with a MediaFileData object when successful. 78 | * @param errorCallback Invoked if the attempt fails, this function. 79 | */ 80 | getFormatData( 81 | successCallback: (data: MediaFileData) => void, 82 | errorCallback?: () => void): void; 83 | } 84 | 85 | /** Encapsulates format information about a media file. */ 86 | interface MediaFileData { 87 | /** The actual format of the audio and video content. */ 88 | codecs: string; 89 | /** The average bitrate of the content. The value is zero for images. */ 90 | bitrate: number; 91 | /** The height of the image or video in pixels. The value is zero for audio clips. */ 92 | height: number; 93 | /** The width of the image or video in pixels. The value is zero for audio clips. */ 94 | width: number; 95 | /** The length of the video or sound clip in seconds. The value is zero for images. */ 96 | duration: number; 97 | } 98 | 99 | /** Encapsulates the error code resulting from a failed media capture operation. */ 100 | interface CaptureError { 101 | /** 102 | * One of the pre-defined error codes listed below. 103 | * CaptureError.CAPTURE_INTERNAL_ERR 104 | * The camera or microphone failed to capture image or sound. 105 | * CaptureError.CAPTURE_APPLICATION_BUSY 106 | * The camera or audio capture application is currently serving another capture request. 107 | * CaptureError.CAPTURE_INVALID_ARGUMENT 108 | * Invalid use of the API (e.g., the value of limit is less than one). 109 | * CaptureError.CAPTURE_NO_MEDIA_FILES 110 | * The user exits the camera or audio capture application before capturing anything. 111 | * CaptureError.CAPTURE_NOT_SUPPORTED 112 | * The requested capture operation is not supported. 113 | */ 114 | code: number; 115 | message: string; 116 | } 117 | 118 | declare var CaptureError: { 119 | /** Constructor for CaptureError */ 120 | new (code: number, message: string): CaptureError; 121 | CAPTURE_INTERNAL_ERR: number; 122 | CAPTURE_APPLICATION_BUSY: number; 123 | CAPTURE_INVALID_ARGUMENT: number; 124 | CAPTURE_NO_MEDIA_FILES: number; 125 | CAPTURE_NOT_SUPPORTED: number; 126 | CAPTURE_PERMISSION_DENIED: number; 127 | } 128 | 129 | /** Encapsulates audio capture configuration options. */ 130 | interface AudioOptions { 131 | /** 132 | * The maximum number of audio clips the device's user can capture in a single 133 | * capture operation. The value must be greater than or equal to 1. 134 | */ 135 | limit?: number; 136 | /** The maximum duration of a audio clip, in seconds. */ 137 | duration?: number; 138 | } 139 | 140 | /** Encapsulates image capture configuration options. */ 141 | interface ImageOptions { 142 | /** 143 | * The maximum number of images the user can capture in a single capture operation. 144 | * The value must be greater than or equal to 1 (defaults to 1). 145 | */ 146 | limit?: number; 147 | } 148 | 149 | /** Encapsulates video capture configuration options. */ 150 | interface VideoOptions { 151 | /** 152 | * The maximum number of video clips the device's user can capture in a single 153 | * capture operation. The value must be greater than or equal to 1. 154 | */ 155 | limit?: number; 156 | /** The maximum duration of a video clip, in seconds. */ 157 | duration?: number; 158 | } 159 | 160 | /** Encapsulates a set of media capture parameters that a device supports. */ 161 | interface ConfigurationData { 162 | /** The ASCII-encoded lowercase string representing the media type. */ 163 | type: string; 164 | /** The height of the image or video in pixels. The value is zero for sound clips. */ 165 | height: number; 166 | /** The width of the image or video in pixels. The value is zero for sound clips. */ 167 | width: number; 168 | } -------------------------------------------------------------------------------- /www/CaptureAudioOptions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * Encapsulates all audio capture operation configuration options. 24 | */ 25 | const CaptureAudioOptions = function () { 26 | // Upper limit of sound clips user can record. Value must be equal or greater than 1. 27 | this.limit = 1; 28 | // Maximum duration of a single sound clip in seconds. 29 | this.duration = 0; 30 | }; 31 | 32 | module.exports = CaptureAudioOptions; 33 | -------------------------------------------------------------------------------- /www/CaptureError.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * The CaptureError interface encapsulates all errors in the Capture API. 24 | */ 25 | const CaptureError = function (c) { 26 | this.code = c || null; 27 | }; 28 | 29 | // Camera or microphone failed to capture image or sound. 30 | CaptureError.CAPTURE_INTERNAL_ERR = 0; 31 | // Camera application or audio capture application is currently serving other capture request. 32 | CaptureError.CAPTURE_APPLICATION_BUSY = 1; 33 | // Invalid use of the API (e.g. limit parameter has value less than one). 34 | CaptureError.CAPTURE_INVALID_ARGUMENT = 2; 35 | // User exited camera application or audio capture application before capturing anything. 36 | CaptureError.CAPTURE_NO_MEDIA_FILES = 3; 37 | // User denied permissions required to perform the capture request. 38 | CaptureError.CAPTURE_PERMISSION_DENIED = 4; 39 | // The requested capture operation is not supported. 40 | CaptureError.CAPTURE_NOT_SUPPORTED = 20; 41 | 42 | module.exports = CaptureError; 43 | -------------------------------------------------------------------------------- /www/CaptureImageOptions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * Encapsulates all image capture operation configuration options. 24 | */ 25 | const CaptureImageOptions = function () { 26 | // Upper limit of images user can take. Value must be equal or greater than 1. 27 | this.limit = 1; 28 | }; 29 | 30 | module.exports = CaptureImageOptions; 31 | -------------------------------------------------------------------------------- /www/CaptureVideoOptions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * Encapsulates all video capture operation configuration options. 24 | */ 25 | const CaptureVideoOptions = function () { 26 | // Upper limit of videos user can record. Value must be equal or greater than 1. 27 | this.limit = 1; 28 | // Maximum duration of a single video clip in seconds. 29 | this.duration = 0; 30 | // Video quality parameter, 0 means low quality, suitable for MMS messages, and value 1 means high quality. 31 | this.quality = 1; 32 | }; 33 | 34 | module.exports = CaptureVideoOptions; 35 | -------------------------------------------------------------------------------- /www/ConfigurationData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * Encapsulates a set of parameters that the capture device supports. 24 | */ 25 | function ConfigurationData () { 26 | // The ASCII-encoded string in lower case representing the media type. 27 | this.type = null; 28 | // The height attribute represents height of the image or video in pixels. 29 | // In the case of a sound clip this attribute has value 0. 30 | this.height = 0; 31 | // The width attribute represents width of the image or video in pixels. 32 | // In the case of a sound clip this attribute has value 0 33 | this.width = 0; 34 | } 35 | 36 | module.exports = ConfigurationData; 37 | -------------------------------------------------------------------------------- /www/MediaFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | const utils = require('cordova/utils'); 23 | const exec = require('cordova/exec'); 24 | const File = require('cordova-plugin-file.File'); 25 | const CaptureError = require('./CaptureError'); 26 | /** 27 | * Represents a single file. 28 | * 29 | * name {DOMString} name of the file, without path information 30 | * fullPath {DOMString} the full path of the file, including the name 31 | * type {DOMString} mime type 32 | * lastModifiedDate {Date} last modified date 33 | * size {Number} size of the file in bytes 34 | */ 35 | const MediaFile = function (name, localURL, type, lastModifiedDate, size) { 36 | MediaFile.__super__.constructor.apply(this, arguments); 37 | }; 38 | 39 | utils.extend(MediaFile, File); 40 | 41 | /** 42 | * Request capture format data for a specific file and type 43 | * 44 | * @param {Function} successCB 45 | * @param {Function} errorCB 46 | */ 47 | MediaFile.prototype.getFormatData = function (successCallback, errorCallback) { 48 | if (typeof this.fullPath === 'undefined' || this.fullPath === null) { 49 | errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); 50 | } else { 51 | exec(successCallback, errorCallback, 'Capture', 'getFormatData', [this.fullPath, this.type]); 52 | } 53 | }; 54 | 55 | module.exports = MediaFile; 56 | -------------------------------------------------------------------------------- /www/MediaFileData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * MediaFileData encapsulates format information of a media file. 24 | * 25 | * @param {DOMString} codecs 26 | * @param {long} bitrate 27 | * @param {long} height 28 | * @param {long} width 29 | * @param {float} duration 30 | */ 31 | const MediaFileData = function (codecs, bitrate, height, width, duration) { 32 | this.codecs = codecs || null; 33 | this.bitrate = bitrate || 0; 34 | this.height = height || 0; 35 | this.width = width || 0; 36 | this.duration = duration || 0; 37 | }; 38 | 39 | module.exports = MediaFileData; 40 | -------------------------------------------------------------------------------- /www/android/init.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | const cordova = require('cordova'); 23 | const helpers = require('./helpers'); 24 | 25 | const SUCCESS_EVENT = 'pendingcaptureresult'; 26 | const FAILURE_EVENT = 'pendingcaptureerror'; 27 | 28 | const sChannel = cordova.addStickyDocumentEventHandler(SUCCESS_EVENT); 29 | const fChannel = cordova.addStickyDocumentEventHandler(FAILURE_EVENT); 30 | 31 | // We fire one of two events in the case where the activity gets killed while 32 | // the user is capturing audio, image, video, etc. in a separate activity 33 | document.addEventListener('deviceready', function () { 34 | document.addEventListener('resume', function (event) { 35 | if (event.pendingResult && event.pendingResult.pluginServiceName === 'Capture') { 36 | if (event.pendingResult.pluginStatus === 'OK') { 37 | const mediaFiles = helpers.wrapMediaFiles(event.pendingResult.result); 38 | sChannel.fire(mediaFiles); 39 | } else { 40 | fChannel.fire(event.pendingResult.result); 41 | } 42 | } 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /www/capture.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | const exec = require('cordova/exec'); 23 | const helpers = require('./helpers'); 24 | 25 | /** 26 | * Launches a capture of different types. 27 | * 28 | * @param (DOMString} type 29 | * @param {Function} successCB 30 | * @param {Function} errorCB 31 | * @param {CaptureVideoOptions} options 32 | */ 33 | function _capture (type, successCallback, errorCallback, options) { 34 | const win = function (pluginResult) { 35 | successCallback(helpers.wrapMediaFiles(pluginResult)); 36 | }; 37 | exec(win, errorCallback, 'Capture', type, [options]); 38 | } 39 | 40 | /** 41 | * The Capture interface exposes an interface to the camera and microphone of the hosting device. 42 | */ 43 | function Capture () { 44 | this.supportedAudioModes = []; 45 | this.supportedImageModes = []; 46 | this.supportedVideoModes = []; 47 | } 48 | 49 | /** 50 | * Launch audio recorder application for recording audio clip(s). 51 | * 52 | * @param {Function} successCB 53 | * @param {Function} errorCB 54 | * @param {CaptureAudioOptions} options 55 | */ 56 | Capture.prototype.captureAudio = function (successCallback, errorCallback, options) { 57 | _capture('captureAudio', successCallback, errorCallback, options); 58 | }; 59 | 60 | /** 61 | * Launch camera application for taking image(s). 62 | * 63 | * @param {Function} successCB 64 | * @param {Function} errorCB 65 | * @param {CaptureImageOptions} options 66 | */ 67 | Capture.prototype.captureImage = function (successCallback, errorCallback, options) { 68 | _capture('captureImage', successCallback, errorCallback, options); 69 | }; 70 | 71 | /** 72 | * Launch device camera application for recording video(s). 73 | * 74 | * @param {Function} successCB 75 | * @param {Function} errorCB 76 | * @param {CaptureVideoOptions} options 77 | */ 78 | Capture.prototype.captureVideo = function (successCallback, errorCallback, options) { 79 | _capture('captureVideo', successCallback, errorCallback, options); 80 | }; 81 | 82 | module.exports = new Capture(); 83 | -------------------------------------------------------------------------------- /www/helpers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | const MediaFile = require('./MediaFile'); 23 | 24 | function wrapMediaFiles (pluginResult) { 25 | const mediaFiles = []; 26 | let i; 27 | for (i = 0; i < pluginResult.length; i++) { 28 | const mediaFile = new MediaFile( 29 | pluginResult[i].name, 30 | // Backwards compatibility 31 | pluginResult[i].localURL || pluginResult[i].fullPath, 32 | pluginResult[i].type, 33 | pluginResult[i].lastModifiedDate, 34 | pluginResult[i].size 35 | ); 36 | mediaFile.fullPath = pluginResult[i].fullPath; 37 | mediaFiles.push(mediaFile); 38 | } 39 | return mediaFiles; 40 | } 41 | 42 | module.exports = { 43 | wrapMediaFiles 44 | }; 45 | --------------------------------------------------------------------------------