diff options
Diffstat (limited to 'plugins')
33 files changed, 10822 insertions, 0 deletions
diff --git a/plugins/org.apache.cordova.media/CONTRIBUTING.md b/plugins/org.apache.cordova.media/CONTRIBUTING.md new file mode 100644 index 00000000..f7dbcaba --- /dev/null +++ b/plugins/org.apache.cordova.media/CONTRIBUTING.md @@ -0,0 +1,37 @@ +<!-- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--> + +# Contributing to Apache Cordova + +Anyone can contribute to Cordova. And we need your contributions. + +There are multiple ways to contribute: report bugs, improve the docs, and +contribute code. + +For instructions on this, start with the +[contribution overview](http://cordova.apache.org/#contribute). + +The details are explained there, but the important items are: + - Sign and submit an Apache ICLA (Contributor License Agreement). + - Have a Jira issue open that corresponds to your contribution. + - Run the tests so your patch doesn't break existing functionality. + +We look forward to your contributions! diff --git a/plugins/org.apache.cordova.media/LICENSE b/plugins/org.apache.cordova.media/LICENSE new file mode 100644 index 00000000..7a4a3ea2 --- /dev/null +++ b/plugins/org.apache.cordova.media/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/NOTICE b/plugins/org.apache.cordova.media/NOTICE new file mode 100644 index 00000000..8ec56a52 --- /dev/null +++ b/plugins/org.apache.cordova.media/NOTICE @@ -0,0 +1,5 @@ +Apache Cordova +Copyright 2012 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/plugins/org.apache.cordova.media/README.md b/plugins/org.apache.cordova.media/README.md new file mode 100644 index 00000000..bacd84b0 --- /dev/null +++ b/plugins/org.apache.cordova.media/README.md @@ -0,0 +1,22 @@ +<!--- + license: Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Plugin documentation: [doc/index.md](doc/index.md) diff --git a/plugins/org.apache.cordova.media/RELEASENOTES.md b/plugins/org.apache.cordova.media/RELEASENOTES.md new file mode 100644 index 00000000..d6fa87fa --- /dev/null +++ b/plugins/org.apache.cordova.media/RELEASENOTES.md @@ -0,0 +1,111 @@ +<!-- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +--> +# Release Notes + +### 0.2.1 (Sept 5, 2013) +* CB-4432 copyright notice change + +### 0.2.3 (Sept 25, 2013) +* CB-4889 bumping&resetting version +* [windows8] commandProxy was moved +* CB-4889 renaming references +* CB-4889 renaming org.apache.cordova.core.media to org.apache.cordova.media +* [CB-4847] iOS 7 microphone access requires user permission - if denied, CDVCapture, CDVSound does not handle it properly +* Rename CHANGELOG.md -> RELEASENOTES.md +* [CB-4799] Fix incorrect JS references within native code on Android & iOS +* Fix compiler/lint warnings +* Rename plugin id from AudioHandler -> media +* [CB-4763] Remove reference to cordova-android's FileHelper. +* [CB-4752] Incremented plugin version on dev branch. + +### 0.2.4 (Oct 9, 2013) +* [CB-4928] plugin-media doesn't load on windows8 +* [CB-4915] Incremented plugin version on dev branch. + +### 0.2.5 (Oct 28, 2013) +* CB-5128: add repo + issue tag to plugin.xml for media plugin +* [CB-5010] Incremented plugin version on dev branch. + + +### 0.2.6 (Dec 4, 2013) +* [ubuntu] specify policy_group +* add ubuntu platform +* Added amazon-fireos platform. Change to use amazon-fireos as a platform if the user agent string contains 'cordova-amazon-fireos' + +### 0.2.7 (Jan 02, 2014) +* CB-5658 Add doc/index.md for Media plugin +* Adding READ_PHONE_STATE to the plugin permissions + +### 0.2.8 (Feb 05, 2014) +* Add preliminary support for Tizen. +* [CB-4755] Fix crash in Media.setVolume on iOS + +### 0.2.9 (Feb 26, 2014) +* CB-6051 Update media plugin to work with new cdvfile:// urls +* CB-5748 Make sure that Media.onStatus is called when recording is started. + +### 0.2.10 (Apr 17, 2014) +* CB-6422: [windows8] use cordova/exec/proxy +* CB-6212: [iOS] fix warnings compiled under arm64 64-bit +* CB-6225: Specify plugin dependency on File plugin 1.0.1 +* CB-6460: Update license headers +* CB-6465: Add license headers to Tizen code +* Add NOTICE file + +### 0.2.11 (Jun 05, 2014) +* CB-6127 Spanish and French Translations added. Github close #13 +* CB-6807 Add license +* CB-6706: Relax dependency on file plugin +* CB-6478: Fix exception when try to record audio file on windows 8 +* CB-6477: Add musicLibrary and microphone capabilities to windows 8 platform +* CB-6491 add CONTRIBUTING.md + +### 0.2.12 (Aug 06, 2014) +* CB-6127 Updated translations for docs +* ios: Make it easier to play media and record audio simultaneously +* code #s for MediaError + +### 0.2.13 (Sep 17, 2014) +* CB-6963 renamed folder to tests + added nested plugin.xml +* added documentation for manual tests +* CB-6963 Port Media manual & automated tests +* CB-6963 Port media tests to plugin-test-framework + +### 0.2.14 (Oct 03, 2014) +* Amazon Specific changes: Added READ_PHONE_STATE permission same as done in Android +* make possible plays wav file +* CB-7638 Get audio duration properly on windows +* CB-7454 Adds support for m4a audio format for Windows +* CB-7547 Fixes audio recording on windows platform +* CB-7531 Fixes play() failure after release() call + +### 0.2.15 (Dec 02, 2014) +* CB-6153 **Android**: Add docs for volume control behaviour, and fix controls not being reset on page navigation +* CB-6153 **Android**: Make volume buttons control music stream while any audio players are created +* CB-7977 Mention `deviceready` in plugin docs +* CB-7945 Made media.spec.15 and media.spec.16 auto tests green +* CB-7700 cordova-plugin-media documentation translation: cordova-plugin-media + +### 0.2.16 (Feb 04, 2015) +* CB-8351 ios: Stop using (newly) deprecated CDVJSON.h +* CB-8351 ios: Use argumentForIndex rather than NSArray extension +* CB-8252 android: Fire audio events from native via message channel +* CB-8152 ios: Remove deprecated methods in Media plugin (deprecated since 2.5) diff --git a/plugins/org.apache.cordova.media/doc/de/index.md b/plugins/org.apache.cordova.media/doc/de/index.md new file mode 100644 index 00000000..e3e6cd73 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/de/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Dieses Plugin bietet die Möglichkeit zum Aufzeichnen und Wiedergeben von audio-Dateien auf einem Gerät. + +**Hinweis**: die aktuelle Implementierung eine W3C-Spezifikation für Medien-Capture nicht einhalten, und wird nur zu Informationszwecken zur Verfügung gestellt. Zukünftiger Implementierungen wird an der aktuellen W3C-Spezifikation und kann die aktuellen APIs entweiht. + +## Installation + + cordova plugin add org.apache.cordova.media + + +## Unterstützte Plattformen + +* Android +* BlackBerry 10 +* iOS +* Windows Phone 7 und 8 +* Tizen +* Windows 8 + +## Windows Phone Macken + +* Nur eine Mediendatei kann gleichzeitig abgespielt werden. + +* Es gibt strenge Beschränkungen, wie Ihre Anwendung mit anderen Medien interagiert. Finden Sie in der [Microsoft-Dokumentation für details][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Medien + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### Parameter + +* **Src**: ein URI mit der audio-Inhalte. *(DOM-String und enthält)* + +* **MediaSuccess**: (Optional) der Rückruf, der nach dem führt ein `Media` -Objekt abgeschlossen hat, die aktuelle Wiedergabe, Aufzeichnung oder Stop-Action. *(Funktion)* + +* **Medienfehler**: (Optional) der Rückruf, der ausgeführt wird, wenn ein Fehler auftritt. *(Funktion)* + +* **MediaStatus**: (Optional) der Rückruf, der ausgeführt wird, um Statusänderungen anzugeben. *(Funktion)* + +### Konstanten + +Die folgenden Konstanten werden gemeldet, als einzigem Parameter an die `mediaStatus` Rückruf: + +* `Media.MEDIA_NONE`= 0; +* `Media.MEDIA_STARTING`= 1; +* `Media.MEDIA_RUNNING`= 2; +* `Media.MEDIA_PAUSED`= 3; +* `Media.MEDIA_STOPPED`= 4; + +### Methoden + +* `media.getCurrentPosition`: Gibt die aktuelle Position in einer Audiodatei. + +* `media.getDuration`: Gibt die Dauer einer Audiodatei. + +* `media.play`: Starten Sie oder fortsetzen Sie der Wiedergabe einer Audiodatei. + +* `media.pause`: Anhalten der Wiedergabe einer Audiodatei. + +* `media.release`: Das zugrunde liegende Betriebssystem audio Ressourcen frei. + +* `media.seekTo`: Verschiebt die Position innerhalb der audio-Datei. + +* `media.setVolume`: Stellen Sie die Lautstärke für die Audiowiedergabe. + +* `media.startRecord`: Starten der Aufnahme einer audio-Datei. + +* `media.stopRecord`: Stoppen Sie die Aufnahme einer audio-Datei. + +* `media.stop`: Abspielen einer Audiodatei zu stoppen. + +### Zusätzliche ReadOnly-Parameter + +* **Position**: die Position innerhalb der audio-Wiedergabe in Sekunden. + + * Nicht während des Spiels automatisch aktualisiert; Rufen Sie `getCurrentPosition` zu aktualisieren. + +* **Dauer**: die Dauer der Medien, in Sekunden. + +## media.getCurrentPosition + +Gibt die aktuelle Position in einer Audiodatei. Aktualisiert auch die `Media` des Objekts `position` Parameter. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### Parameter + +* **MediaSuccess**: der Rückruf, der die aktuelle Position in Sekunden übergeben wird. + +* **Medienfehler**: (Optional) der Rückruf ausgeführt, wenn ein Fehler auftritt. + +### Kurzes Beispiel + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +Gibt die Dauer einer Audiodatei in Sekunden. Wenn die Dauer unbekannt ist, wird der Wert-1 zurückgegeben. + + media.getDuration(); + + +### Kurzes Beispiel + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## Media.Pause + +Pausen Abspielen einer Audiodatei. + + media.pause(); + + +### Kurzes Beispiel + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## Media.Play + +Startet oder setzt fort, Abspielen einer Audiodatei. + + media.play(); + + +### Kurzes Beispiel + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS Macken + +* **NumberOfLoops**: übergeben Sie diese Option, um die `play` -Methode können Sie die Anzahl der angeben soll die Mediendatei ausspielen, z.B.: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **PlayAudioWhenScreenIsLocked**: übergeben Sie diese Option, um die `play` -Methode können Sie angeben, ob Sie möchten Wiedergabe zu ermöglichen, wenn der Bildschirm gesperrt ist. Wenn legen Sie auf `true` (der Standardwert), der Zustand der die mute Taste wird ignoriert, z.B.: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **Reihenfolge der Dateisuche**: Wenn nur ein Dateiname oder Pfad angegeben wird, sucht iOS in das `www` Verzeichnis für die Datei, dann in der Anwendung `documents/tmp` Verzeichnis: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +Das zugrunde liegende Betriebssystem audio Ressourcen frei. Dies ist besonders wichtig für Android, da gibt es eine begrenzte Anzahl von OpenCore-Instanzen für die Medienwiedergabe. Anwendungen rufen die `release` -Funktion für alle `Media` Ressource, die nicht mehr benötigt wird. + + media.release(); + + +### Kurzes Beispiel + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Legt die aktuelle Position in einer Audiodatei. + + media.seekTo(milliseconds); + + +### Parameter + +* **Millisekunden**: die Position die Wiedergabeposition innerhalb des Audiotracks in Millisekunden festgelegt. + +### Kurzes Beispiel + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### BlackBerry 10 Macken + +* BlackBerry OS 5-Geräten unterstützt nicht. + +## media.setVolume + +Stellen Sie die Lautstärke für eine audio-Datei. + + media.setVolume(volume); + + +### Parameter + +* **Lautstärke**: die Lautstärke für Wiedergabe fest. Der Wert muss im Bereich zwischen 0,0 und 1,0 liegen. + +### Unterstützte Plattformen + +* Android +* iOS + +### Kurzes Beispiel + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Beginnt mit der Aufnahme einer audio-Datei. + + media.startRecord(); + + +### Unterstützte Plattformen + +* Android +* iOS +* Windows Phone 7 und 8 +* Windows 8 + +### Kurzes Beispiel + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Android Eigenarten + +* Android-Geräte aufnehmen Audio im Adaptive Sprachcodecs Format. Die angegebene Datei sollte mit einer Endung *.amr* enden. + +### iOS Macken + +* iOS nur Datensätze, die Dateien des Typs *WAV* und gibt ein Fehler, wenn die Dateinamen-Erweiterung ist richtig nicht. + +* Wenn ein vollständiger Pfad nicht angegeben ist, wird die Aufzeichnung in der Anwendung platziert `documents/tmp` Verzeichnis. Erreichbar über die `File` -API verwenden `LocalFileSystem.TEMPORARY` . Allen Unterverzeichnissen in Rekordzeit angegeben muss bereits vorhanden sein. + +* Dateien können aufgezeichnet und spielte mit die Dokumenten URI zurück: + + var myMedia = new Media("documents://beer.mp3") + + +### Windows 8 Macken + +* Wenn Sie ein vollständiger Pfad nicht angegeben ist, wird die Aufnahme im AppData/Temp-Verzeichnis platziert. Erreichbar über die `Datei` API verwenden `LocalFileSystem.TEMPORARY` oder "ms-Appdata: / / / Temp /<filename>' URI. + +* Allen Unterverzeichnissen in Rekordzeit angegeben muss bereits vorhanden sein. + +### Tizen Macken + +* Tizen Geräten unterstützt nicht. + +## media.stop + +Beendet die Wiedergabe einer Audiodatei. + + Media.Stop(); + + +### Kurzes Beispiel + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Stoppt die Aufnahme einer audio-Datei. + + media.stopRecord(); + + +### Unterstützte Plattformen + +* Android +* iOS +* Windows Phone 7 und 8 +* Windows 8 + +### Kurzes Beispiel + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Tizen Macken + +* Tizen Geräten unterstützt nicht. + +## Medienfehler + +A `MediaError` Objekt wird zurückgegeben, um die `mediaError` Callback-Funktion, wenn ein Fehler auftritt. + +### Eigenschaften + +* **Code**: einer der vordefinierten Fehlercodes aufgeführt. + +* **Nachricht**: eine Fehlermeldung beschreibt die Details des Fehlers. + +### Konstanten + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/es/index.md b/plugins/org.apache.cordova.media/doc/es/index.md new file mode 100644 index 00000000..ce5d8dd0 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/es/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Este plugin proporciona la capacidad de grabar y reproducir archivos de audio en un dispositivo. + +**Nota**: la implementación actual no se adhiere a una especificación del W3C para la captura de los medios de comunicación y se proporciona únicamente para su comodidad. Una futura implementación se adherirá a la más reciente especificación W3C y puede desaprueban las API actuales. + +## Instalación + + cordova plugin add org.apache.cordova.media + + +## Plataformas soportadas + +* Android +* BlackBerry 10 +* iOS +* Windows Phone 7 y 8 +* Tizen +* Windows 8 + +## Windows Phone rarezas + +* Archivo único multimedia puede reproducir en un momento. + +* Hay restricciones estrictas sobre cómo interactúa la aplicación con otros medios. Consulte la [documentación de Microsoft para obtener más detalles][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Los medios de comunicación + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### Parámetros + +* **src**: un URI que contiene el contenido de audio. *(DOMString)* + +* **mediaSuccess**: (opcional) la devolución de llamada que se ejecuta después de que un objeto `Media` ha completado el juego actual, registro o acción. *(Function)* + +* **mediaError**: (opcional) la devolución de llamada que se ejecuta si se produce un error. *(Función)* + +* **mediaStatus**: (opcional) la devolución de llamada que se ejecuta para indicar cambios en el estado. *(Función)* + +### Constantes + +Las siguientes constantes son reportadas como el único parámetro para la devolución de llamada `mediaStatus`: + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED` = 3; +* `Media.MEDIA_STOPPED` = 4; + +### Métodos + +* `media.getCurrentPosition`: devuelve la posición actual dentro de un archivo de audio. + +* `media.getDuration`: devuelve la duración de un archivo de audio. + +* `media.play`: iniciar o reanudar reproducción de un archivo de audio. + +* `media.pause`: pausar la reproducción de un archivo de audio. + +* `media.release`: libera recursos de audio del sistema operativo subyacente. + +* `media.seekTo`: mueve la posición dentro del archivo de audio. + +* `media.setVolume`: ajuste el volumen de reproducción de audio. + +* `media.startRecord`: iniciar la grabación de un archivo de audio. + +* `media.stopRecord`: dejar de grabar un archivo de audio. + +* `media.stop`: deja de jugar a un archivo de audio. + +### Parámetros adicionales ReadOnly + +* **posición**: la posición dentro de la reproducción de audio, en segundos. + + * No actualizada automáticamente durante la reproducción; Llame a `getCurrentPosition` para actualizar. + +* **duration**: la duración de los medios de comunicación, en segundos. + +## media.getCurrentPosition + +Devuelve la posición actual dentro de un archivo de audio. También actualiza el `Media` del objeto `position` parámetro. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### Parámetros + +* **mediaSuccess**: la devolución de llamada que se pasa a la posición actual en segundos. + +* **mediaError**: (opcional) la devolución de llamada para ejecutar si se produce un error. + +### Ejemplo rápido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +Devuelve la duración de un archivo de audio en segundos. Si se desconoce la duración, devuelve un valor de -1. + + media.getDuration(); + + +### Ejemplo rápido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## media.pause + +Pausas jugando un archivo de audio. + + media.pause(); + + +### Ejemplo rápido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## media.play + +Inicia o reanuda la reproducción de un archivo de audio. + + media.play(); + + +### Ejemplo rápido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS rarezas + +* **numberOfLoops**: pasar esta opción al método `play` para especificar el número de veces que desea que los medios de archivo para jugar, por ejemplo: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**: pasar en esta opción el método `play` para especificar si desea permitir la reproducción cuando la pantalla está bloqueada. Si se omite establecido en `true` (el valor predeterminado), el estado del botón mute hardware, por ejemplo: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **orden de búsqueda de archivos**: cuando se proporciona sólo un nombre de archivo o ruta simple, iOS busca en el directorio `www` para el archivo, luego en el directorio de la aplicación `documents/tmp`: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +Libera los recursos de audio del sistema operativo subyacente. Esto es particularmente importante para Android, ya que hay una cantidad finita de instancias de OpenCore para la reproducción multimedia. Las aplicaciones deben llamar a la función de `release` para cualquier recurso `Media` que ya no es necesario. + + media.release(); + + +### Ejemplo rápido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Establece la posición actual dentro de un archivo de audio. + + media.seekTo(milliseconds); + + +### Parámetros + +* **milliseconds**: la posición para ajustar la posición de reproducción en el audio, en milisegundos. + +### Ejemplo rápido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### BlackBerry 10 rarezas + +* No compatible con dispositivos BlackBerry OS 5. + +## media.setVolume + +Ajustar el volumen para un archivo de audio. + + media.setVolume(volume); + + +### Parámetros + +* **volume**: el volumen para la reproducción. El valor debe estar dentro del rango de 0.0 a 1.0. + +### Plataformas soportadas + +* Android +* iOS + +### Ejemplo rápido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Empieza a grabar un archivo de audio. + + media.startRecord(); + + +### Plataformas soportadas + +* Android +* iOS +* Windows Phone 7 y 8 +* Windows 8 + +### Ejemplo rápido + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Rarezas Android + +* Dispositivos Android grabación audio en formato Adaptive Multi-rate. El archivo especificado debe terminar con una extensión de *.amr*. + +### iOS rarezas + +* iOS únicos registros a archivos de tipo *.wav* y devuelve un error si el archivo de extensión el nombre es no es correcto. + +* Si no se proporciona una ruta completa, la grabación se coloca en el directorio de la aplicación `documents/tmp`. Esto se puede acceder mediante el `File` API utilizando `LocalFileSystem.TEMPORARY`. Ya debe existir cualquier subdirectorio especificado en un tiempo récord. + +* Archivos pueden ser grabados y jugó de nuevo usando los documentos URI: + + var myMedia = new Media("documents://beer.mp3") + + +### Rarezas de Windows 8 + +* Si no se proporciona una ruta completa, la grabación se coloca en el directorio AppData/temp. Esto puede accederse a través de la `Archivo` Usando API `LocalFileSystem.TEMPORARY` o ' ms-appdata: temporal / / / /<filename>' URI. + +* Ya debe existir cualquier subdirectorio especificado en un tiempo récord. + +### Rarezas Tizen + +* No compatible con dispositivos Tizen. + +## media.stop + +Deja de jugar a un archivo de audio. + + media.stop(); + + +### Ejemplo rápido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Detiene la grabación de un archivo de audio. + + media.stopRecord(); + + +### Plataformas soportadas + +* Android +* iOS +* Windows Phone 7 y 8 +* Windows 8 + +### Ejemplo rápido + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Rarezas Tizen + +* No compatible con dispositivos Tizen. + +## MediaError + +A `MediaError` objeto es devuelto a la `mediaError` función de devolución de llamada cuando se produce un error. + +### Propiedades + +* **code**: uno de los códigos de error predefinido enumerados a continuación. + +* **mensaje**: un mensaje de error que describe los detalles del error. + +### Constantes + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/fr/index.md b/plugins/org.apache.cordova.media/doc/fr/index.md new file mode 100644 index 00000000..291c13b1 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/fr/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Ce plugin permet d'enregistrer et de lire des fichiers audio sur un périphérique. + +**Remarque**: l'implémentation actuelle n'est pas conforme à une spécification du W3C pour la capture de médias et est fournie pour plus de commodité seulement. Une prochaine implémentation adhèrera à la toute dernière spécification du W3C, ce qui aura probablement pour effet de déprécier l'API actuelle. + +## Installation + + cordova plugin add org.apache.cordova.media + + +## Plates-formes prises en charge + +* Android +* BlackBerry 10 +* iOS +* Windows Phone 7 et 8 +* Paciarelli +* Windows 8 + +## Windows Phone Quirks + +* Un seul fichier média peut être lu à la fois. + +* Il y a des restrictions strictes concernant la façon dont votre application interagit avec d'autres médias. Consultez la [documentation de Microsoft pour plus d'informations][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Media + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### Paramètres + +* **src** : l'URI du contenu audio. *(DOMString)* + +* **mediaSuccess** : (facultative) la fonction callback exécutée après que la lecture en cours, l'action d'enregistrement ou l'arrêt de lecture de l'objet `Media` soit terminée. *(Function)* + +* **mediaError** : (facultative) la fonction callback exécutée si une erreur survient. *(Function)* + +* **mediaStatus** : (facultative) la fonction callback exécutée lors de chaque changement d'état. *(Function)* + +### Constantes + +Les constantes suivantes correspondent au seul paramètre transmis à la fonction callback `mediaStatus` : + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED` = 3; +* `Media.MEDIA_STOPPED` = 4; + +### Méthodes + +* `media.getCurrentPosition` : retourne la position de lecture dans un fichier audio. + +* `media.getDuration`: retourne la durée d'un fichier audio. + +* `media.play` : permet de commencer ou reprendre la lecture d'un fichier audio. + +* `media.pause` : interrompt la lecture d'un fichier audio. + +* `media.release` : libère les ressources audio correspondantes du système d'exploitation. + +* `media.seekTo` : déplace la position de lecture au sein du fichier audio. + +* `media.setVolume` : permet de régler le volume du clip audio. + +* `media.startRecord` : commence l'enregistrement d'un fichier audio. + +* `media.stopRecord` : arrête l'enregistrement d'un fichier audio. + +* `media.stop` : arrête la lecture d'un fichier audio. + +### Paramètres supplémentaires en lecture seule + +* **position** : la position de lecture sein du clip audio, en secondes. + + * La valeur n'est pas automatiquement rafraichie pendant la lecture ; un appel à `getCurrentPosition` permet sa mise à jour. + +* **duration** : la durée du média, en secondes. + +## media.getCurrentPosition + +Retourne la position courante dans un fichier audio. Met également à jour la `Media` de l'objet `position` paramètre. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### Paramètres + +* **mediaSuccess** : la fonction callback à laquelle est transmise la position actuelle exprimée en secondes. + +* **mediaError** : (facultative) la fonction callback exécutée si une erreur se produit. + +### Petit exemple + + // lecteur audio + // + var my_media = new Media(src, onSuccess, onError); + + // met à jour la position de lecture du fichier à chaque seconde + var mediaTimer = setInterval(function () { + // récupère la position + my_media.getCurrentPosition( + // fonction callback de succès + function (position) { + if (position > -1) { + console.log((position) + " secondes"); + } + }, + // fonction callback d'erreur + function (e) { + console.log("Erreur lors de l'obtention de la position : " + e); + } + ); + }, 1000); + + +## media.getDuration + +Retourne la durée d'un fichier audio en quelques secondes. Si on ne connaît pas la durée, elle retourne la valeur -1. + + media.getDuration(); + + +### Petit exemple + + // lecteur audio + // + var my_media = new Media(src, onSuccess, onError); + + // récupère la durée + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " secondes"; + } + }, 100); + + +## media.pause + +Suspend la lecture d'un fichier audio. + + media.pause(); + + +### Petit exemple + + // joue le clip audio + // + function playAudio(url) { + // joue le fichier audio situé à cette url + var my_media = new Media(url, + // fonction callback de succès + function () { console.log("playAudio() : clip audio joué avec succès"); }, + // error callback + function (err) { console.log("playAudio() : erreur lors de la lecture du clip audio: " + err); } + ); + + // lance la lecture du clip audio + my_media.play(); + + // met la lecture en pause après 10 secondes + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## media.play + +Commence ou reprend la lecture d'un fichier audio. + + media.play(); + + +### Petit exemple + + // joue le clip audio + // + function playAudio(url) { + // joue le fichier audio situé à cette url + var my_media = new Media(url, + // fonction callback de succès + function () { + console.log("playAudio() : fichier audio lu avec succès"); + }, + // fonction callback d'erreur + function (err) { + console.log("playAudio() : erreur lors de la lecture du fichier audio : " + err); + } + ); + // commence la lecture du clip audio + my_media.play(); + } + + +### iOS Quirks + +* **numberOfLoops** : transmettre cette option à la méthode `play` permet de spécifier le nombre de lectures à la suite d'un fichier donné, par exemple : + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked** : transmettre cette option à la méthode `play` permet de spécifier si la lecture doit continuer même lorsque l'écran de l'appareil est verrouillé. Si la valeur est `true` (par défaut), le bouton matériel mute est ignoré, par exemple : + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **ordre de recherche de fichier** : si un nom de fichier ou chemin d'accès simple est fourni, iOS recherche d'abord le fichier correspondant dans le répertoire `www`, puis dans le répertoire `documents/tmp` appartenant à l'application : + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // recherche d'abord le fichier www/audio/beer.mp3 puis <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +Libère certaines ressources audio du système d'exploitation. Cela est particulièrement important pour Android, puisqu'il y a une quantité finie d'instances OpenCore pour la lecture du média. Les applications doivent en général appeler cette fonction `release` pour toute ressource `Media` qui n'est plus nécessaire. + + media.release(); + + +### Petit exemple + + // lecteur audio + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Définit la position de lecture actuelle dans un fichier audio. + + media.seekTo(milliseconds); + + +### Paramètres + +* **milliseconds** : la nouvelle position de lecture au sein du fichier audio, en millisecondes. + +### Petit exemple + + // lecteur audio + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // avance la position à 10 secondes du début du fichier après 5 secondes + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### BlackBerry 10 Quirks + +* Cette méthode n'est pas prise en charge sur les périphériques BlackBerry OS 5. + +## media.setVolume + +Permet de régler le volume d'un fichier audio. + + media.setVolume(volume); + + +### Paramètres + +* **volume** : le volume à utiliser pour la lecture. La valeur doit être comprise entre 0.0 et 1.0 inclus. + +### Plates-formes prises en charge + +* Android +* iOS + +### Petit exemple + + // joue le clip audio + // + function playAudio(url) { + // joue le fichier audio situé à cette url + var my_media = new Media(url, + // fonction callback de succès + function() { + console.log("playAudio() : fichier audio lu avec succès"); + }, + // fonction callback d'erreur + function(err) { + console.log("playAudio() : erreur lors de la lecture du fichier audio : " + err); + }); + + // lance la lecture du clip audio + my_media.play(); + + // baisse le volume au maximum après 2 secondes + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // monte le volume à 1.0 (maximum) après 5 secondes + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Permet de démarrer l'enregistrement d'un fichier audio. + + media.startRecord(); + + +### Plates-formes prises en charge + +* Android +* iOS +* Windows Phone 7 et 8 +* Windows 8 + +### Petit exemple + + // enregistrement audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // fonction callback de succès + function() { + console.log("recordAudio() : audio enregistré avec succès"); + }, + + // fonction callback d'erreur + function(err) { + console.log("recordAudio() : erreur lors de l'enregistrement audio : " + err.code); + }); + + // débute l'enregistrement audio + mediaRec.startRecord(); + } + + +### Quirks Android + +* Les appareils Android enregistrent de l'audio au format Adaptive Multi-Rate. Le nom de fichier spécifié doit donc comporter une extension *.amr*. + +### iOS Quirks + +* iOS produit uniquement des enregistrements sous la forme de fichier de type *.wav* et renvoie une erreur si l'extension du nom de fichier est incorrecte. + +* Si un chemin d'accès complet n'est pas précisé, l'enregistrement est placé dans le répertoire `documents/tmp` correspondant à l'application. Il sera ensuite accessible via l'API `File` en utilisant la constante `LocalFileSystem.TEMPORARY`. Tout sous-répertoire présent dans le chemin d'accès au moment de l'enregistrement doit déjà exister. + +* Les fichiers peuvent être enregistrés et lus à l'aide de l'URI des documents : + + var myMedia = new Media("documents://beer.mp3") + + +### Bizarreries de Windows 8 + +* Si un chemin d'accès complet n'est pas fourni, l'enregistrement est placé dans le répertoire AppData/temp. Ce qui peut être consulté le `Fichier` À l'aide de l'API `LocalFileSystem.TEMPORARY` ou ' ms-appdata : temp / / / /<filename>' URI. + +* N'importe quel sous-répertoire spécifié au moment de l'enregistrement doit déjà exister. + +### Bizarreries de paciarelli + +* Pas pris en charge sur les appareils paciarelli. + +## media.stop + +Arrête la lecture d'un fichier audio. + + media.stop(); + + +### Petit exemple + + // joue le clip audio + // + function playAudio(url) { + // joue le fichier audio situé à cette url + var my_media = new Media(url, + // fonction callback de succès + function() { + console.log("playAudio() : clip audio lu avec succès"); + }, + // fonction callback d'erreur + function(err) { + console.log("playAudio() : erreur lors de la lecture du clip audio : " + err); + } + ); + + // démarre la lecture du clip audio + my_media.play(); + + // arrête la lecture après 10 secondes + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Arrête l'enregistrement d'un fichier audio. + + media.stopRecord(); + + +### Plates-formes prises en charge + +* Android +* iOS +* Windows Phone 7 et 8 +* Windows 8 + +### Petit exemple + + // enregistrement audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // fonction callback de succès + function() { + console.log("recordAudio() : audio enregistré avec succès"); + }, + + // fonction callback d'erreur + function(err) { + console.log("recordAudio() : erreur lors de l'enregistrement audio : " + err.code); + } + ); + + // débute l'enregistrement audio + mediaRec.startRecord(); + + // arrête l'enregistrement après 10 secondes + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Bizarreries de paciarelli + +* Pas pris en charge sur les appareils paciarelli. + +## MediaError + +Un objet `MediaError` est transmis à la fonction callback `mediaError` lorsqu'une erreur survient. + +### Propriétés + +* **code**: l'un des codes d'erreur prédéfinis énumérés ci-dessous. + +* **message**: un message d'erreur décrivant les détails de l'erreur. + +### Constantes + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/index.md b/plugins/org.apache.cordova.media/doc/index.md new file mode 100644 index 00000000..297d1e72 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/index.md @@ -0,0 +1,508 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +This plugin provides the ability to record and play back audio files on a device. + +__NOTE__: The current implementation does not adhere to a W3C +specification for media capture, and is provided for convenience only. +A future implementation will adhere to the latest W3C specification +and may deprecate the current APIs. + +This plugin defines a global `Media` Constructor. + +Although in the global scope, it is not available until after the `deviceready` event. + + document.addEventListener("deviceready", onDeviceReady, false); + function onDeviceReady() { + console.log(Media); + } + +## Installation + + cordova plugin add org.apache.cordova.media + +## Supported Platforms + +- Android +- BlackBerry 10 +- iOS +- Windows Phone 7 and 8 +- Tizen +- Windows 8 + +## Windows Phone Quirks + +- Only one media file can be played back at a time. + +- There are strict restrictions on how your application interacts with other media. See the [Microsoft documentation for details][url]. + +[url]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Media + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + +### Parameters + +- __src__: A URI containing the audio content. _(DOMString)_ + +- __mediaSuccess__: (Optional) The callback that executes after a `Media` object has completed the current play, record, or stop action. _(Function)_ + +- __mediaError__: (Optional) The callback that executes if an error occurs. _(Function)_ + +- __mediaStatus__: (Optional) The callback that executes to indicate status changes. _(Function)_ + +### Constants + +The following constants are reported as the only parameter to the +`mediaStatus` callback: + +- `Media.MEDIA_NONE` = 0; +- `Media.MEDIA_STARTING` = 1; +- `Media.MEDIA_RUNNING` = 2; +- `Media.MEDIA_PAUSED` = 3; +- `Media.MEDIA_STOPPED` = 4; + +### Methods + +- `media.getCurrentPosition`: Returns the current position within an audio file. + +- `media.getDuration`: Returns the duration of an audio file. + +- `media.play`: Start or resume playing an audio file. + +- `media.pause`: Pause playback of an audio file. + +- `media.release`: Releases the underlying operating system's audio resources. + +- `media.seekTo`: Moves the position within the audio file. + +- `media.setVolume`: Set the volume for audio playback. + +- `media.startRecord`: Start recording an audio file. + +- `media.stopRecord`: Stop recording an audio file. + +- `media.stop`: Stop playing an audio file. + +### Additional ReadOnly Parameters + +- __position__: The position within the audio playback, in seconds. + - Not automatically updated during play; call `getCurrentPosition` to update. + +- __duration__: The duration of the media, in seconds. + + +## media.getCurrentPosition + +Returns the current position within an audio file. Also updates the `Media` object's `position` parameter. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + +### Parameters + +- __mediaSuccess__: The callback that is passed the current position in seconds. + +- __mediaError__: (Optional) The callback to execute if an error occurs. + +### Quick Example + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +Returns the duration of an audio file in seconds. If the duration is unknown, it returns a value of -1. + + + media.getDuration(); + +### Quick Example + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## media.pause + +Pauses playing an audio file. + + media.pause(); + + +### Quick Example + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## media.play + +Starts or resumes playing an audio file. + + media.play(); + + +### Quick Example + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS Quirks + +- __numberOfLoops__: Pass this option to the `play` method to specify + the number of times you want the media file to play, e.g.: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + +- __playAudioWhenScreenIsLocked__: Pass in this option to the `play` + method to specify whether you want to allow playback when the screen + is locked. If set to `true` (the default value), the state of the + hardware mute button is ignored, e.g.: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + +- __order of file search__: When only a file name or simple path is + provided, iOS searches in the `www` directory for the file, then in + the application's `documents/tmp` directory: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + +## media.release + +Releases the underlying operating system's audio resources. +This is particularly important for Android, since there are a finite amount of +OpenCore instances for media playback. Applications should call the `release` +function for any `Media` resource that is no longer needed. + + media.release(); + + +### Quick Example + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Sets the current position within an audio file. + + media.seekTo(milliseconds); + +### Parameters + +- __milliseconds__: The position to set the playback position within the audio, in milliseconds. + + +### Quick Example + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### BlackBerry 10 Quirks + +- Not supported on BlackBerry OS 5 devices. + +## media.setVolume + +Set the volume for an audio file. + + media.setVolume(volume); + +### Parameters + +- __volume__: The volume to set for playback. The value must be within the range of 0.0 to 1.0. + +### Supported Platforms + +- Android +- iOS + +### Quick Example + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Starts recording an audio file. + + media.startRecord(); + +### Supported Platforms + +- Android +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +### Quick Example + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Android Quirks + +- Android devices record audio in Adaptive Multi-Rate format. The specified file should end with a _.amr_ extension. +- The hardware volume controls are wired up to the media volume while any Media objects are alive. Once the last created Media object has `release()` called on it, the volume controls revert to their default behaviour. The controls are also reset on page navigation, as this releases all Media objects. + +### iOS Quirks + +- iOS only records to files of type _.wav_ and returns an error if the file name extension is not correct. + +- If a full path is not provided, the recording is placed in the application's `documents/tmp` directory. This can be accessed via the `File` API using `LocalFileSystem.TEMPORARY`. Any subdirectory specified at record time must already exist. + +- Files can be recorded and played back using the documents URI: + + var myMedia = new Media("documents://beer.mp3") + +### Windows 8 Quirks + +- If a full path is not provided, the recording is placed in the AppData/temp directory. This can be accessed via the `File` API using `LocalFileSystem.TEMPORARY` or 'ms-appdata:///temp/<filename>' URI. + +- Any subdirectory specified at record time must already exist. + +### Tizen Quirks + +- Not supported on Tizen devices. + +## media.stop + +Stops playing an audio file. + + media.stop(); + +### Quick Example + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Stops recording an audio file. + + media.stopRecord(); + +### Supported Platforms + +- Android +- iOS +- Windows Phone 7 and 8 +- Windows 8 + +### Quick Example + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Tizen Quirks + +- Not supported on Tizen devices. + +## MediaError + +A `MediaError` object is returned to the `mediaError` callback +function when an error occurs. + +### Properties + +- __code__: One of the predefined error codes listed below. + +- __message__: An error message describing the details of the error. + +### Constants + +- `MediaError.MEDIA_ERR_ABORTED` = 1 +- `MediaError.MEDIA_ERR_NETWORK` = 2 +- `MediaError.MEDIA_ERR_DECODE` = 3 +- `MediaError.MEDIA_ERR_NONE_SUPPORTED` = 4 + diff --git a/plugins/org.apache.cordova.media/doc/it/index.md b/plugins/org.apache.cordova.media/doc/it/index.md new file mode 100644 index 00000000..50da3aaf --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/it/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Questo plugin consente di registrare e riprodurre i file audio su un dispositivo. + +**Nota**: l'implementazione attuale non aderisce a una specifica del W3C per l'acquisizione di mezzi e viene fornito solo per comodità. Una futura realizzazione aderirà alla specifica W3C più recente e può deprecare le API corrente. + +## Installazione + + cordova plugin add org.apache.cordova.media + + +## Piattaforme supportate + +* Android +* BlackBerry 10 +* iOS +* Windows Phone 7 e 8 +* Tizen +* Windows 8 + +## Stranezze di Windows Phone + +* File sola multimediale può essere riprodotti in un momento. + +* Ci sono severe restrizioni su come l'applicazione interagisce con altri media. Vedere la [documentazione di Microsoft per maggiori dettagli][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Media + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### Parametri + +* **src**: un URI contenente il contenuto audio. *(DOMString)* + +* **mediaSuccess**: (facoltativo) il callback che viene eseguito dopo un `Media` oggetto ha completato il gioco corrente, record o interrompere l'azione. *(Funzione)* + +* **errore mediaError**: (facoltativo) il callback che viene eseguito se si verifica un errore. *(Funzione)* + +* **mediaStatus**: (facoltativo) il callback che viene eseguito per indicare i cambiamenti di stato. *(Funzione)* + +### Costanti + +Costanti sono segnalate come unico parametro per il `mediaStatus` callback: + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED` = 3; +* `Media.MEDIA_STOPPED` = 4; + +### Metodi + +* `media.getCurrentPosition`: Restituisce la posizione corrente all'interno di un file audio. + +* `media.getDuration`: Restituisce la durata di un file audio. + +* `media.play`: Iniziare o riprendere la riproduzione di un file audio. + +* `media.pause`: Pausa la riproduzione di un file audio. + +* `media.release`: Libera risorse audio del sistema operativo sottostante. + +* `media.seekTo`: Sposta la posizione all'interno del file audio. + +* `media.setVolume`: Impostare il volume per la riproduzione audio. + +* `media.startRecord`: Iniziare a registrare un file audio. + +* `media.stopRecord`: Interrompere la registrazione di un file audio. + +* `media.stop`: Interrompere la riproduzione di un file audio. + +### Parametri supplementari ReadOnly + +* **posizione**: la posizione all'interno della riproduzione audio, in pochi secondi. + + * Non aggiornate automaticamente durante il gioco; chiamare `getCurrentPosition` per l'aggiornamento. + +* **durata**: la durata dei media, in secondi. + +## media.getCurrentPosition + +Restituisce la posizione corrente all'interno di un file audio. Aggiorna anche il `Media` dell'oggetto `position` parametro. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### Parametri + +* **mediaSuccess**: il callback passato la posizione corrente in pochi secondi. + +* **errore mediaError**: (facoltativo) il callback da eseguire se si verifica un errore. + +### Esempio rapido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +Restituisce la durata di un file audio in secondi. Se la durata è sconosciuta, essa restituisce un valore di -1. + + media.getDuration(); + + +### Esempio rapido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## Media.pause + +Sospende la riproduzione di un file audio. + + media.pause(); + + +### Esempio rapido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## Media.Play + +Avvia o riprende la riproduzione di un file audio. + + media.play(); + + +### Esempio rapido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS stranezze + +* **numberOfLoops**: passare questa opzione per il `play` metodo per specificare il numero di volte desiderato file multimediale per riprodurre, ad esempio: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**: questa opzione per passare il `play` metodo per specificare se si desidera consentire la riproduzione quando lo schermo è bloccato. Se impostato su `true` (il valore predefinito), viene ignorato lo stato del pulsante mute hardware, ad esempio: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **ordine di ricerca di file**: quando viene fornito solo un nome file o percorso semplice, cerca in iOS il `www` directory per il file, quindi l'applicazione `documents/tmp` directory: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +Rilascia le risorse audio del sistema operativo sottostante. Ciò è particolarmente importante per Android, dato che ci sono una quantità finita di OpenCore istanze per la riproduzione multimediale. Le applicazioni devono chiamare il `release` funzione per qualsiasi `Media` risorsa che non è più necessario. + + media.release(); + + +### Esempio rapido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Imposta la posizione corrente all'interno di un file audio. + + media.seekTo(milliseconds); + + +### Parametri + +* **millisecondi**: posizione per impostare la posizione di riproduzione all'interno l'audio, in millisecondi. + +### Esempio rapido + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### BlackBerry 10 capricci + +* Non è supportato sui dispositivi BlackBerry OS 5. + +## media.setVolume + +Impostare il volume per un file audio. + + media.setVolume(volume); + + +### Parametri + +* **volume**: il volume impostato per la riproduzione. Il valore deve essere all'interno della gamma di 0,0 e 1,0. + +### Piattaforme supportate + +* Android +* iOS + +### Esempio rapido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Avvia la registrazione di un file audio. + + media.startRecord(); + + +### Piattaforme supportate + +* Android +* iOS +* Windows Phone 7 e 8 +* Windows 8 + +### Esempio rapido + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Stranezze Android + +* Dispositivi Android registrano audio in formato Adaptive Multi-Rate. Il file specificato deve terminare con l'estensione ** . + +### iOS stranezze + +* iOS solo i record per i file di tipo *WAV* e restituisce un errore se il file di nome estensione è non corretto. + +* Se non è specificato un percorso completo, la registrazione viene inserita nell'applicazione `documents/tmp` directory. Questo si può accedere tramite il `File` API utilizzando `LocalFileSystem.TEMPORARY` . Deve esistere alcuna sottodirectory specificate a tempo di record. + +* I file possono essere registrati e giocati indietro usando i documenti URI: + + var myMedia = new Media("documents://beer.mp3") + + +### Stranezze di Windows 8 + +* Se non è specificato un percorso completo, la registrazione viene inserita nella directory AppData/temp. Questo si può accedere tramite il `File` Utilizzando API `LocalFileSystem.TEMPORARY` o ' ms-appdata: / / temp /<filename>' URI. + +* Deve esistere alcuna sottodirectory specificate a tempo di record. + +### Tizen stranezze + +* Tizen periferiche non supportano. + +## media.stop + +Interrompe la riproduzione di un file audio. + + Media.Stop(); + + +### Esempio rapido + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Smette di registrare un file audio. + + media.stopRecord(); + + +### Piattaforme supportate + +* Android +* iOS +* Windows Phone 7 e 8 +* Windows 8 + +### Esempio rapido + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Tizen stranezze + +* Tizen periferiche non supportano. + +## Errore MediaError + +A `MediaError` oggetto viene restituito alla `mediaError` funzione di callback quando si verifica un errore. + +### Proprietà + +* **codice**: uno dei codici di errore predefiniti elencati di seguito. + +* **messaggio**: un messaggio di errore che descrive i dettagli dell'errore. + +### Costanti + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/ja/index.md b/plugins/org.apache.cordova.media/doc/ja/index.md new file mode 100644 index 00000000..29c63e6d --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/ja/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +このプラグインは、記録し、デバイス上のオーディオ ファイルを再生する機能を提供します。 + +**注**: 現在の実装では、メディアのキャプチャのための W3C 仕様に準拠していないとは便宜上提供されるだけです。 将来の実装を最新の W3C 仕様に準拠し、現在 Api をとがめることがあります。 + +## インストール + + cordova plugin add org.apache.cordova.media + + +## サポートされているプラットフォーム + +* アンドロイド +* ブラックベリー 10 +* iOS +* Windows Phone 7 と 8 +* Tizen +* Windows 8 + +## Windows Phone の癖 + +* のみ 1 つのメディア ファイルは、一度に再生できます。 + +* アプリケーションと他のメディアとの対話に厳格な制限があります。 [詳細については、Microsoft のマニュアル][1]を参照してください。. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## メディア + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### パラメーター + +* **src**: オーディオのコンテンツを含む URI。*(,)* + +* **mediaSuccess**: (省略可能) 後に実行するコールバックを `Media` 再生用に現在、レコード、または stop アクション オブジェクトが完了しました。*(機能)* + +* **mediaError**: (省略可能) エラーが発生した場合に実行されるコールバック。*(機能)* + +* **mediaStatus**: (省略可能) 状態の変化を示すために実行されるコールバック。*(機能)* + +### 定数 + +次の定数を唯一のパラメーターとして報告されます、 `mediaStatus` コールバック。 + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED` = 3; +* `Media.MEDIA_STOPPED` = 4; + +### メソッド + +* `media.getCurrentPosition`: オーディオ ファイル内の現在位置を返します。 + +* `media.getDuration`: オーディオ ファイルの継続時間を返します。 + +* `media.play`: 開始またはオーディオ ファイルの再生を再開します。 + +* `media.pause`: オーディオ ファイルの再生を一時停止。 + +* `media.release`: 基になるオペレーティング システムのオーディオ リソースを解放します。 + +* `media.seekTo`: オーディオ ファイル内の位置を移動します。 + +* `media.setVolume`: オーディオの再生ボリュームを設定します。 + +* `media.startRecord`: オーディオ ファイルの録音を開始します。 + +* `media.stopRecord`: オーディオ ファイルの録音を停止します。 + +* `media.stop`: オーディオ ファイルの再生を停止します。 + +### 追加読み取り専用パラメーター + +* **位置**: 数秒でオーディオの再生では、内の位置。 + + * 自動的に更新されません; のプレイ中にコール `getCurrentPosition` を更新します。 + +* **期間**: 秒で、メディアの期間。 + +## media.getCurrentPosition + +オーディオ ファイル内の現在位置を返します。また更新して、 `Media` オブジェクトの `position` パラメーター。 + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### パラメーター + +* **mediaSuccess**: 秒の現在の位置を渡されるコールバック。 + +* **mediaError**: (省略可能) コールバックでエラーが発生した場合に実行します。 + +### 簡単な例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +オーディオ ファイルの継続時間 (秒単位) を返します。期間は知られている、-1 の値が返されます。 + + media.getDuration(); + + +### 簡単な例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## media.pause + +オーディオ ファイルの再生を一時停止します。 + + media.pause(); + + +### 簡単な例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## media.play + +開始またはオーディオ ファイルの再生を再開します。 + + media.play(); + + +### 簡単な例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS の癖 + +* **numberOfLoops**: このオプションを指定して、 `play` メディア ファイルを再生する、例えば回数を指定する方法。 + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**: このオプションを渡す、 `play` 、画面がロックされているときに再生を許可するかどうかを指定するメソッド。 場合に設定されている `true` (既定値)、例えば、ハードウェア ミュート ボタンの状態は無視されます。 + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **ファイル検索の順序**: iOS の検索でファイル名または単純なパスのみが提供される場合、 `www` ディレクトリ、ファイルをアプリケーションの `documents/tmp` ディレクトリ。 + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +基になるオペレーティング システムのオーディオ リソースを解放します。 メディアの再生のための OpenCore インスタンスの有限な量があるので、人造人間のため特に重要です。 アプリケーションを呼び出す必要があります、 `release` 任意の関数 `Media` は、もはや必要なリソースです。 + + media.release(); + + +### 簡単な例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +オーディオ ファイル内の現在位置を設定します。 + + media.seekTo(milliseconds); + + +### パラメーター + +* **ミリ秒単位**: ミリ秒単位で、オーディオの再生位置を設定する位置。 + +### 簡単な例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### ブラックベリー 10 癖 + +* ブラックベリー OS 5 デバイスでサポートされていません。 + +## media.setVolume + +オーディオ ファイルの音量を設定します。 + + media.setVolume(volume); + + +### パラメーター + +* **ボリューム**: ボリュームの再生を設定します。値は 0.0 ~ 1.0 の範囲内である必要があります。 + +### サポートされているプラットフォーム + +* アンドロイド +* iOS + +### 簡単な例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +オーディオ ファイルの録音を開始します。 + + media.startRecord(); + + +### サポートされているプラットフォーム + +* アンドロイド +* iOS +* Windows Phone 7 と 8 +* Windows 8 + +### 簡単な例 + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Android の癖 + +* Android 端末適応型マルチレート形式にオーディオを録音します。指定したファイルは、 *.amr*拡張子で終わる必要があります。 + +### iOS の癖 + +* iOS の種類*.wav*と返しますエラー場合は、ファイル名拡張子がファイルをレコードのみが修正されません。 + +* 記録は、アプリケーションの配置の完全なパスを指定しない場合 `documents/tmp` ディレクトリ。 これを介してアクセスすることができます、 `File` API を使用して `LocalFileSystem.TEMPORARY` 。 記録時に指定された任意のサブディレクトリに存在する必要があります。 + +* ファイルを記録し、再生することができますドキュメント URI を使用して。 + + var myMedia = new Media("documents://beer.mp3") + + +### Windows 8 の癖 + +* 完全なパスを指定しない場合、記録は AppData/temp ディレクトリに配置されます。これを介してアクセスすることができます、 `ファイル` API を使用してください。 `LocalFileSystem.TEMPORARY` または ' ms appdata: temp////<filename>' URI。 + +* 記録時に指定された任意のサブディレクトリに存在する必要があります。 + +### Tizen の癖 + +* Tizen のデバイスでサポートされていません。 + +## media.stop + +オーディオ ファイルの再生を停止します。 + + media.stop(); + + +### 簡単な例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +オーディオ ファイルの録音を停止します。 + + media.stopRecord(); + + +### サポートされているプラットフォーム + +* アンドロイド +* iOS +* Windows Phone 7 と 8 +* Windows 8 + +### 簡単な例 + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Tizen の癖 + +* Tizen のデバイスでサポートされていません。 + +## MediaError + +A `MediaError` オブジェクトに返される、 `mediaError` コールバック関数でエラーが発生したとき。 + +### プロパティ + +* **コード**: 次のいずれかの定義済みのエラー コード。 + +* **メッセージ**: エラーの詳細を説明するエラー メッセージ。 + +### 定数 + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/ko/index.md b/plugins/org.apache.cordova.media/doc/ko/index.md new file mode 100644 index 00000000..3a7a908c --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/ko/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +이 플러그인 기록 장치에 오디오 파일을 재생 하는 기능을 제공 합니다. + +**참고**: 현재 구현 미디어 캡처에 대 한 W3C 사양을 준수 하지 않는 및 편의 위해서만 제공 됩니다. 미래 구현 최신 W3C 사양을 준수 한다 고 현재 Api 사용 중지 될 수 있습니다. + +## 설치 + + cordova plugin add org.apache.cordova.media + + +## 지원 되는 플랫폼 + +* 안 드 로이드 +* 블랙베리 10 +* iOS +* Windows Phone 7과 8 +* Tizen +* 윈도우 8 + +## Windows Phone 단점 + +* 한 번에 하나의 미디어 파일을 다시 재생할 수 있습니다. + +* 응용 프로그램 다른 미디어와 상호 작용 하는 방법에 대 한 엄격한 제한이 있다. [자세한 내용은 Microsoft 문서][1] 를 참조 하십시오. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## 미디어 + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### 매개 변수 + +* **src**: 오디오 콘텐츠를 포함 하는 URI. *(DOMString)* + +* **mediaSuccess**: (선택 사항) 후 실행 되는 콜백 한 `Media` 개체 현재 재생, 기록, 또는 중지 작업을 완료 했습니다. *(기능)* + +* **mediaError**: (선택 사항) 오류가 발생 하면 실행 되는 콜백. *(기능)* + +* **mediaStatus**: (선택 사항) 상태 변화를 나타내기 위해 실행 하는 콜백. *(기능)* + +### 상수 + +다음 상수를 유일한 매개 변수로 보고 되는 `mediaStatus` 콜백: + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED` = 3; +* `Media.MEDIA_STOPPED` = 4; + +### 메서드 + +* `media.getCurrentPosition`: 오디오 파일 내에서 현재 위치를 반환합니다. + +* `media.getDuration`: 오디오 파일의 기간을 반환합니다. + +* `media.play`: 시작 또는 오디오 파일 재생을 다시 시작 합니다. + +* `media.pause`: 오디오 파일의 재생을 일시 중지 합니다. + +* `media.release`: 기본 운영 체제의 오디오 리소스를 해제합니다. + +* `media.seekTo`: 오디오 파일 내에서 위치를 이동합니다. + +* `media.setVolume`: 오디오 재생 볼륨을 설정 합니다. + +* `media.startRecord`: 오디오 파일을 녹음을 시작 합니다. + +* `media.stopRecord`: 오디오 파일 기록을 중지 합니다. + +* `media.stop`: 오디오 파일 재생을 중지 합니다. + +### 추가 읽기 전용 매개 변수 + +* **위치**: 위치 오디오 재생 시간 (초). + + * 플레이; 하는 동안 자동으로 업데이트 전화 `getCurrentPosition` 를 업데이트 합니다. + +* **기간**: 기간, 매체의 초. + +## media.getCurrentPosition + +오디오 파일 내에서 현재 위치를 반환합니다. 또한 업데이트는 `Media` 개체의 `position` 매개 변수. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### 매개 변수 + +* **mediaSuccess**: 현재 위치 (초) 전달 되는 콜백. + +* **mediaError**: (선택 사항) 콜백 실행 오류가 발생 하는 경우에. + +### 빠른 예제 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +초 오디오 파일의 기간을 반환합니다. 기간을 알 수 없는 경우-1 값을 반환 합니다. + + media.getDuration(); + + +### 빠른 예제 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## media.pause + +오디오 파일 재생을 일시 중지 합니다. + + media.pause(); + + +### 빠른 예제 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## media.play + +시작 또는 오디오 파일 재생을 다시 시작 합니다. + + media.play(); + + +### 빠른 예제 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS 단점 + +* **numberOfLoops**:이 옵션을 전달할는 `play` 시간을 재생 하려면, 예를 들어 미디어 파일의 수를 지정 하는 방법: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**:이 옵션을 전달할는 `play` 메서드는 화면이 잠겨 때 재생 수 있도록 지정 하려면. 만약 설정 `true` (기본값) 하드웨어 음소거 버튼의 상태, 예를 들면 무시 됩니다: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **파일 검색의 순서**: iOS에서 검색 한 파일 이름 또는 간단한 경로 제공 하는 경우는 `www` 파일을 다음 응용 프로그램의 디렉터리 `documents/tmp` 디렉터리: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +기본 운영 체제의 오디오 리소스를 해제합니다. 이것은 유한 양의 미디어 재생용 OpenCore 인스턴스 때문에 안 드 로이드를 위해 특히 중요 하다입니다. 응용 프로그램 호출 해야는 `release` 함수에 대 한 `Media` 리소스를 더 이상 필요 합니다. + + media.release(); + + +### 빠른 예제 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +오디오 파일 내의 현재 위치를 설정합니다. + + media.seekTo(milliseconds); + + +### 매개 변수 + +* **밀리초**: 밀리초에서는 오디오에서 재생 위치를 설정 하는 위치. + +### 빠른 예제 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### 블랙베리 10 단점 + +* 블랙베리 OS 5 장치에서 지원 되지 않습니다. + +## media.setVolume + +오디오 파일의 볼륨을 설정 합니다. + + media.setVolume(volume); + + +### 매개 변수 + +* **볼륨**: 볼륨 재생을 위한 설정. 값은 0.0에서 1.0의 범위 내에서 해야 합니다. + +### 지원 되는 플랫폼 + +* 안 드 로이드 +* iOS + +### 빠른 예제 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +오디오 파일 녹음을 시작 합니다. + + media.startRecord(); + + +### 지원 되는 플랫폼 + +* 안 드 로이드 +* iOS +* Windows Phone 7과 8 +* 윈도우 8 + +### 빠른 예제 + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### 안 드 로이드 단점 + +* 안 드 로이드 장치 적응 다중 속도 형식에서 오디오를 기록합니다. 지정 된 파일 *.amr* 확장명으로 끝나야 합니다. + +### iOS 단점 + +* iOS만 레코드 형식을 *.wav* 및 반환 오류 경우 파일 이름 확장명의 파일을 수정 하지. + +* 전체 경로 제공 하지 않으면 응용 프로그램의 기록 배치 됩니다 `documents/tmp` 디렉터리. 이 통해 액세스할 수 있는 `File` API를 사용 하 여 `LocalFileSystem.TEMPORARY` . 기록 시간에 지정 된 하위 디렉터리에 이미 존재 해야 합니다. + +* 파일을 기록 하 고 재생할 수 있습니다 문서 URI를 사용 하 여 다시: + + var myMedia = new Media("documents://beer.mp3") + + +### 윈도우 8 단점 + +* 전체 경로 제공 하지 않으면 녹음 AppData/temp 디렉터리에 배치 됩니다. 이 통해 액세스할 수 있는 `파일` API를 사용 하 여 `LocalFileSystem.TEMPORARY` 또는 ' ms appdata: 온도 / / / /<filename>' URI. + +* 기록 시간에 지정 된 하위 디렉터리에 이미 존재 해야 합니다. + +### Tizen 특수 + +* Tizen 장치에서 지원 되지 않습니다. + +## media.stop + +오디오 파일 재생을 중지 합니다. + + media.stop(); + + +### 빠른 예제 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +오디오 파일 녹음을 중지 합니다. + + media.stopRecord(); + + +### 지원 되는 플랫폼 + +* 안 드 로이드 +* iOS +* Windows Phone 7과 8 +* 윈도우 8 + +### 빠른 예제 + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Tizen 특수 + +* Tizen 장치에서 지원 되지 않습니다. + +## MediaError + +A `MediaError` 개체에 반환 됩니다는 `mediaError` 콜백 함수 오류가 발생 합니다. + +### 속성 + +* **코드**: 미리 정의 된 오류 코드 중 하나가 아래에 나열 된. + +* **메시지**: 오류 세부 정보를 설명 하는 오류 메시지. + +### 상수 + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/pl/index.md b/plugins/org.apache.cordova.media/doc/pl/index.md new file mode 100644 index 00000000..81fdb311 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/pl/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Plugin daje możliwość nagrywania i odtwarzania plików audio na urządzeniu. + +**Uwaga**: Obecna implementacja nie stosować się do specyfikacji W3C do przechwytywania mediów i jest dostarczane jedynie dla wygody. Przyszłej realizacji będą przylegać do najnowszych specyfikacji W3C i może potępiać bieżące interfejsów API. + +## Instalacja + + cordova plugin add org.apache.cordova.media + + +## Obsługiwane platformy + +* Android +* BlackBerry 10 +* iOS +* Windows Phone 7 i 8 +* Tizen +* Windows 8 + +## Windows Phone dziwactwa + +* Tylko jeden plik mogą być zagrany w tył w czasie. + +* Istnieją ścisłe ograniczenia na jak aplikacja współdziała z innymi mediami. Zobacz [Microsoft dokumentacji szczegóły][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Media + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### Parametry + +* **src**: URI zawierający zawartość audio. *(DOMString)* + +* **mediaSuccess**: (opcjonalne) wywołania zwrotnego, który wykonuje po `Media` obiektu została zakończona bieżącej gry, rekordu lub działania stop. *(Funkcja)* + +* **mediaError**: (opcjonalne) wywołania zwrotnego, która wykonuje w przypadku wystąpienia błędu. *(Funkcja)* + +* **mediaStatus**: (opcjonalne) wywołania zwrotnego, który wykonuje wskazać zmiany statusu. *(Funkcja)* + +### Stałe + +Poniższe stałe są zgłaszane jako parametr tylko do `mediaStatus` wywołania zwrotnego: + +* `Media.MEDIA_NONE`= 0; +* `Media.MEDIA_STARTING`= 1; +* `Media.MEDIA_RUNNING`= 2; +* `Media.MEDIA_PAUSED`= 3; +* `Media.MEDIA_STOPPED`= 4; + +### Metody + +* `media.getCurrentPosition`: Zwraca bieżącej pozycji w pliku audio. + +* `media.getDuration`: Zwraca czas trwania pliku audio. + +* `media.play`: Rozpoczęcie lub wznowienie odtwarzania pliku audio. + +* `media.pause`: Wstrzymanie odtwarzania pliku audio. + +* `media.release`: Zwalnia zasoby audio system operacyjny. + +* `media.seekTo`: Porusza się pozycji w pliku audio. + +* `media.setVolume`: Ustaw głośność odtwarzania dźwięku. + +* `media.startRecord`: Nagrywanie pliku audio. + +* `media.stopRecord`: Zatrzymaj nagrywanie pliku audio. + +* `media.stop`: Zatrzymania odtwarzania pliku audio. + +### Parametry dodatkowe ReadOnly + +* **stanowisko**: stanowisko w odtwarzaniu dźwięku, w kilka sekund. + + * Nie jest automatycznie aktualizowana podczas odtwarzania; wywołanie `getCurrentPosition` aktualizacji. + +* **czas**: trwania mediów, w kilka sekund. + +## media.getCurrentPosition + +Zwraca bieżącą pozycję w pliku audio. Również aktualizacje `Media` obiektu `position` parametr. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### Parametry + +* **mediaSuccess**: wywołania zwrotnego, który jest przekazywany bieżącej pozycji w kilka sekund. + +* **mediaError**: (opcjonalne) wywołanie zwrotne do wykonać, jeśli wystąpi błąd. + +### Szybki przykład + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +Zwraca czas trwania pliku audio w kilka sekund. Jeśli czas trwania jest nieznane, to zwraca wartość -1. + + media.getDuration(); + + +### Szybki przykład + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## Media.Pause + +Wstrzymuje odtwarzanie pliku audio. + + media.pause(); + + +### Szybki przykład + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## Media.play + +Rozpoczyna się lub wznawia odtwarzanie pliku audio. + + media.play(); + + +### Szybki przykład + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### Dziwactwa iOS + +* **numberOfLoops**: przekazać tę opcję, aby `play` Metoda, aby określić ile razy chcesz, pliku multimedialnego do gry, np.: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**: przekazać tę opcję, aby `play` Metoda, aby określić, czy chcesz umożliwić odtwarzanie, gdy ekran jest zablokowana. Jeśli zestaw `true` (wartość domyślna), stan przycisku Wycisz sprzętu jest ignorowane, np.: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **kolejność wyszukiwania plików**: gdy tylko nazwa pliku lub ścieżka prosta pod warunkiem, iOS wyszukiwania w `www` katalogu, pliku, a następnie w aplikacji `documents/tmp` katalogu: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +Zwalnia zasoby audio system operacyjny. Jest to szczególnie ważne dla systemu Android, ponieważ istnieje skończona ilość podstawie OpenCore wystąpień do odtwarzania multimediów. Aplikacje powinny wywoływać `release` funkcja dla każdego `Media` zasób, który nie jest już potrzebna. + + media.release(); + + +### Szybki przykład + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Ustawia bieżącej pozycji w pliku audio. + + media.seekTo(milliseconds); + + +### Parametry + +* **milisekund**: stanowisko ustala pozycję odtwarzania w audio, w milisekundach. + +### Szybki przykład + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### Jeżyna 10 dziwactwa + +* Nie obsługiwane na urządzeniach BlackBerry OS w wersji 5. + +## media.setVolume + +Ustaw głośność pliku audio. + + media.setVolume(volume); + + +### Parametry + +* **wielkość**: wielkość ustawić odtwarzanie. Wartość musi być z zakresu od 0.0 do 1.0. + +### Obsługiwane platformy + +* Android +* iOS + +### Szybki przykład + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Rozpoczyna nagrywanie pliku audio. + + media.startRecord(); + + +### Obsługiwane platformy + +* Android +* iOS +* Windows Phone 7 i 8 +* Windows 8 + +### Szybki przykład + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Dziwactwa Androida + +* Urządzenia z systemem Android nagrywanie dźwięku w formacie Adaptive Multi-Rate. Określony plik powinien kończyć się rozszerzeniem *AMR* . + +### Dziwactwa iOS + +* iOS tylko rekordy do plików typu *.wav* i zwraca błąd, jeśli nazwa pliku rozszerzenie jest nie prawidłowe. + +* Jeśli nie podano pełną ścieżkę, nagrywanie jest umieszczony w aplikacji `documents/tmp` katalogu. To mogą być dostępne za pośrednictwem `File` za pomocą interfejsu API `LocalFileSystem.TEMPORARY` . Każdy podkatalog określony w rekordowym czasie musi już istnieć. + +* Pliki mogą być zapisywane i grał z powrotem za pomocą dokumentów URI: + + var myMedia = new Media("documents://beer.mp3") + + +### Windows 8 dziwactwa + +* Jeśli nie podano pełną ścieżkę, nagrywanie jest umieszczony w katalogu AppData/temp. To mogą być dostępne za pośrednictwem `Plik` Za pomocą interfejsu API `LocalFileSystem.TEMPORARY` lub "ms-appdata: temp / / / /<filename>"URI. + +* Każdy podkatalog określony w rekordowym czasie musi już istnieć. + +### Dziwactwa Tizen + +* Nie obsługiwane na Tizen urządzenia. + +## media.stop + +Zatrzymuje odtwarzanie pliku audio. + + Media.stop(); + + +### Szybki przykład + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Zatrzymuje nagrywanie pliku audio. + + media.stopRecord(); + + +### Obsługiwane platformy + +* Android +* iOS +* Windows Phone 7 i 8 +* Windows 8 + +### Szybki przykład + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Dziwactwa Tizen + +* Nie obsługiwane na Tizen urządzenia. + +## MediaError + +A `MediaError` obiekt jest zwracany do `mediaError` funkcji wywołania zwrotnego, gdy wystąpi błąd. + +### Właściwości + +* **Kod**: jeden z kodów błędów wstępnie zdefiniowanych poniżej. + +* **wiadomość**: komunikat o błędzie, opisując szczegóły błędu. + +### Stałe + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/ru/index.md b/plugins/org.apache.cordova.media/doc/ru/index.md new file mode 100644 index 00000000..facda0f8 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/ru/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +Этот плагин предоставляет возможность записывать и воспроизводить аудио файлы на устройство. + +**Примечание**: Текущая реализация не соответствует спецификации W3C для захвата СМИ и предоставляется только для удобства. Будущее осуществление будет придерживаться последней спецификации W3C и может Опознайте текущих API. + +## Установка + + cordova plugin add org.apache.cordova.media + + +## Поддерживаемые платформы + +* Android +* BlackBerry 10 +* iOS +* Windows Phone 7 и 8 +* Tizen +* Windows 8 + +## Windows Phone причуды + +* Только один файл может воспроизводиться одновременно. + +* Существуют строгие ограничения в отношении как ваше приложение взаимодействует с другими средствами массовой информации. Смотрите в [документации Microsoft для подробной информации][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## Аудио и видео + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### Параметры + +* **src**: URI, содержащий аудио-контент. *(DOMString)* + +* **mediaSuccess**: (необязательно) обратного вызова, который выполняется после `Media` объект завершения текущего воспроизведения, записи или стоп действий. *(Функция)* + +* **mediaError**: (необязательно) обратного вызова, который выполняется при возникновении ошибки. *(Функция)* + +* **mediaStatus**: (необязательно) обратного вызова, который выполняется для отображения изменений состояния. *(Функция)* + +### Константы + +Следующие константы сообщается как единственный параметр для `mediaStatus` обратного вызова: + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED` = 3; +* `Media.MEDIA_STOPPED` = 4; + +### Методы + +* `media.getCurrentPosition`: Возвращает текущую позицию в аудиофайл. + +* `media.getDuration`: Возвращает продолжительность звукового файла. + +* `media.play`: Начать или возобновить воспроизведение звукового файла. + +* `media.pause`: Приостановка воспроизведения звукового файла. + +* `media.release`: Выпускает аудио ресурсы базовой операционной системы. + +* `media.seekTo`: Перемещает положение в пределах звукового файла. + +* `media.setVolume`: Задайте громкость воспроизведения звука. + +* `media.startRecord`: Начните запись звукового файла. + +* `media.stopRecord`: Остановите запись аудио файлов. + +* `media.stop`: Остановка воспроизведения звукового файла. + +### Дополнительные ReadOnly параметры + +* **позиции**: позиция в аудио воспроизведения в секундах. + + * Не автоматически обновляются во время игры; Вызовите `getCurrentPosition` для обновления. + +* **Продолжительность**: продолжительность СМИ, в секундах. + +## media.getCurrentPosition + +Возвращает текущую позицию в звуковой файл. Также обновляет `Media` объекта `position` параметр. + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### Параметры + +* **mediaSuccess**: обратный вызов, который передается в текущую позицию в секундах. + +* **mediaError**: (необязательно) обратного вызова для выполнения, если происходит ошибка. + +### Краткий пример + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +Возвращает продолжительность аудио файла в секундах. Если длительность неизвестна, она возвращает значение -1. + + media.getDuration(); + + +### Краткий пример + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## Media.Pause + +Приостанавливает воспроизведение звукового файла. + + media.pause(); + + +### Краткий пример + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## Media.Play + +Запускает или возобновляет воспроизведение звукового файла. + + media.play(); + + +### Краткий пример + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### Особенности iOS + +* **numberOfLoops**: этот параметр, чтобы передать `play` метод, чтобы указать количество раз, вы хотите, чтобы средства массовой информации файла для воспроизведения, например: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**: передайте этот параметр для `play` метод, чтобы указать, хотите ли вы разрешить воспроизведение, когда экран заблокирован. Если значение `true` (значение по умолчанию), состояние оборудования безгласную кнопку игнорируется, например: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **Порядок поиска файла**: когда предоставляется только имя файла или простой путь, iOS ищет в `www` каталог для файла, а затем в приложении `documents/tmp` каталога: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +Освобождает ресурсы аудио базовой операционной системы. Это особенно важно для Android, так как существует конечное количество экземпляров OpenCore для воспроизведения мультимедиа. Приложения должны вызвать `release` функция для любого `Media` ресурс, который больше не нужен. + + media.release(); + + +### Краткий пример + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +Задает текущую позицию в течение звукового файла. + + media.seekTo(milliseconds); + + +### Параметры + +* **МС**: позиции задать позицию воспроизведения в аудио, в миллисекундах. + +### Краткий пример + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### Особенности BlackBerry 10 + +* Не поддерживается на устройствах BlackBerry OS 5. + +## media.setVolume + +Задайте громкость звукового файла. + + media.setVolume(volume); + + +### Параметры + +* **объем**: тома, чтобы задать для воспроизведения. Значение должно быть в диапазоне от 0.0 до 1.0. + +### Поддерживаемые платформы + +* Android +* iOS + +### Краткий пример + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +Начинает запись аудио файлов. + + media.startRecord(); + + +### Поддерживаемые платформы + +* Android +* iOS +* Windows Phone 7 и 8 +* Windows 8 + +### Краткий пример + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Особенности Android + +* Android устройств записи аудио в формате адаптивной мульти ставка. Указанный файл должен заканчиваться *.amr* расширение. + +### Особенности iOS + +* iOS только записи в файлы типа *.wav* и возвращает ошибку, если расширение не исправить. + +* Если полный путь не указан, запись помещается в приложения `documents/tmp` каталог. Это могут быть доступны через `File` API с помощью `LocalFileSystem.TEMPORARY` . Любой подкаталог, указанный на время записи должны уже существовать. + +* Файлы могут быть и сыграны записываются обратно, используя документы URI: + + var myMedia = new Media("documents://beer.mp3") + + +### Совместимости Windows 8 + +* Если не указан полный путь, запись помещается в каталоге AppData/temp. Это могут быть доступны через `Файл` С помощью API `LocalFileSystem.TEMPORARY` или ' ms-appdata: / / / temp /<filename>' URI. + +* Любой подкаталог указанного в рекордное время должна уже существовать. + +### Особенности Tizen + +* Не поддерживается на устройствах Tizen. + +## media.stop + +Останавливает воспроизведение звукового файла. + + media.stop(); + + +### Краткий пример + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +Прекращает запись аудио файлов. + + media.stopRecord(); + + +### Поддерживаемые платформы + +* Android +* iOS +* Windows Phone 7 и 8 +* Windows 8 + +### Краткий пример + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### Особенности Tizen + +* Не поддерживается на устройствах Tizen. + +## MediaError + +A `MediaError` объект возвращается к `mediaError` функции обратного вызова при возникновении ошибки. + +### Параметры + +* **code**: один из стандартных кодов ошибок, перечисленных ниже. + +* **сообщение**: сообщение об ошибке, с подробными сведениями об ошибке. + +### Константы + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/doc/zh/index.md b/plugins/org.apache.cordova.media/doc/zh/index.md new file mode 100644 index 00000000..c94104a8 --- /dev/null +++ b/plugins/org.apache.cordova.media/doc/zh/index.md @@ -0,0 +1,494 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# org.apache.cordova.media + +這個外掛程式提供錄製和播放設備上的音訊檔的能力。 + +**注**: 當前的實現並不遵循 W3C 規範的媒體捕獲和僅用於提供方便。 將來的實現將堅持以最新的 W3C 規範和可能棄用當前 Api。 + +## 安裝 + + cordova plugin add org.apache.cordova.media + + +## 支援的平臺 + +* Android 系統 +* 黑莓 10 +* iOS +* Windows Phone 7 和 8 +* Tizen +* Windows 8 + +## Windows Phone 怪癖 + +* 只有一個媒體檔案,可以播放一次。 + +* 沒有嚴格限制對您的應用程式與其他媒體的對話模式。 請參見[Microsoft 文檔的詳細資訊][1]. + + [1]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx + +## 媒體 + + var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]); + + +### 參數 + +* **src**: 包含音訊內容的 URI。*() DOMString* + +* **mediaSuccess**: (可選) 後執行的回檔 `Media` 物件已完成當前戲劇、 記錄或停止行動。*(函數)* + +* **mediaError**: (可選) 如果錯誤發生時執行的回檔。*(函數)* + +* **mediaStatus**: (可選) 執行以指示狀態的更改的回檔。*(函數)* + +### 常量 + +以下常量作為唯一的參數到據報告 `mediaStatus` 回檔: + +* `Media.MEDIA_NONE` = 0; +* `Media.MEDIA_STARTING` = 1; +* `Media.MEDIA_RUNNING` = 2; +* `Media.MEDIA_PAUSED`= 3 ; +* `Media.MEDIA_STOPPED`= 4 ; + +### 方法 + +* `media.getCurrentPosition`: 返回一個音訊檔內的當前位置。 + +* `media.getDuration`: 返回一個音訊檔的持續時間。 + +* `media.play`: 啟動或繼續播放音訊檔。 + +* `media.pause`: 暫停播放的音訊檔。 + +* `media.release`: 釋放底層作業系統的音訊資源。 + +* `media.seekTo`: 在音訊檔內移動的位置。 + +* `media.setVolume`: 設置音訊播放的音量。 + +* `media.startRecord`: 開始錄製的音訊檔。 + +* `media.stopRecord`: 停止錄製的音訊檔。 + +* `media.stop`: 停止播放音訊檔。 + +### 附加唯讀參數 + +* **位置**: 內音訊播放,以秒為單位的位置。 + + * 不會自動更新期間播放 ;調用 `getCurrentPosition` 來更新。 + +* **持續時間**: 媒體的持續時間以秒為單位。 + +## media.getCurrentPosition + +返回一個音訊檔內的當前位置。此外可以更新 `Media` 物件的 `position` 參數。 + + media.getCurrentPosition(mediaSuccess, [mediaError]); + + +### 參數 + +* **mediaSuccess**: 傳遞的當前的位置,以秒為單位的回檔。 + +* **mediaError**: (可選) 回檔執行如果發生錯誤。 + +### 快速的示例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Update media position every second + var mediaTimer = setInterval(function () { + // get media position + my_media.getCurrentPosition( + // success callback + function (position) { + if (position > -1) { + console.log((position) + " sec"); + } + }, + // error callback + function (e) { + console.log("Error getting pos=" + e); + } + ); + }, 1000); + + +## media.getDuration + +以秒為單位返回一個音訊檔的持續時間。如果持續時間是未知的則傳回值為-1。 + + media.getDuration(); + + +### 快速的示例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + // Get duration + var counter = 0; + var timerDur = setInterval(function() { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = my_media.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('audio_duration').innerHTML = (dur) + " sec"; + } + }, 100); + + +## media.pause + +暫停播放音訊檔。 + + media.pause(); + + +### 快速的示例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { console.log("playAudio():Audio Success"); }, + // error callback + function (err) { console.log("playAudio():Audio Error: " + err); } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function () { + media.pause(); + }, 10000); + } + + +## media.play + +開始或重新開始播放音訊檔。 + + media.play(); + + +### 快速的示例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function () { + console.log("playAudio():Audio Success"); + }, + // error callback + function (err) { + console.log("playAudio():Audio Error: " + err); + } + ); + // Play audio + my_media.play(); + } + + +### iOS 的怪癖 + +* **numberOfLoops**: 傳遞到此選項 `play` 方法,以指定的次數,你想讓媒體檔案以播放,例如: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ numberOfLoops: 2 }) + + +* **playAudioWhenScreenIsLocked**: 通過此選項可在 `play` 方法,以指定您是否要允許播放時螢幕鎖定。 如果設置為 `true` (預設值),將忽略硬體靜音按鈕的狀態,例如: + + var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3") + myMedia.play({ playAudioWhenScreenIsLocked : false }) + + +* **檔搜索順序**: 當只有一個檔的名稱或簡單路徑提供時,搜索中的 iOS `www` 目錄為該檔,然後在應用程式中的 `documents/tmp` 目錄: + + var myMedia = new Media("audio/beer.mp3") + myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3 + + +## media.release + +釋放底層作業系統的音訊資源。 這是特別重要的 android 作業系統,因為有了有限數量的 OpenCore 實例播放媒體。 應用程式應當調用 `release` 函數的任何 `Media` 不再需要的資源。 + + media.release(); + + +### 快速的示例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + + my_media.play(); + my_media.stop(); + my_media.release(); + + +## media.seekTo + +在音訊檔中設置的當前的位置。 + + media.seekTo(milliseconds); + + +### 參數 + +* **毫秒為單位)**: 要以毫秒為單位設置中,音訊的播放位置的位置。 + +### 快速的示例 + + // Audio player + // + var my_media = new Media(src, onSuccess, onError); + my_media.play(); + // SeekTo to 10 seconds after 5 seconds + setTimeout(function() { + my_media.seekTo(10000); + }, 5000); + + +### 黑莓 10 怪癖 + +* 黑莓 OS 5 設備上不支援。 + +## media.setVolume + +設置音訊檔的音量。 + + media.setVolume(volume) ; + + +### 參數 + +* **體積**: 要為播放設置的卷。值必須在 0.0 到 1.0 的範圍內。 + +### 支援的平臺 + +* Android 系統 +* iOS + +### 快速的示例 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + }); + + // Play audio + my_media.play(); + + // Mute volume after 2 seconds + setTimeout(function() { + my_media.setVolume('0.0'); + }, 2000); + + // Set volume to 1.0 after 5 seconds + setTimeout(function() { + my_media.setVolume('1.0'); + }, 5000); + } + + +## media.startRecord + +開始錄製的音訊檔。 + + media.startRecord() ; + + +### 支援的平臺 + +* Android 系統 +* iOS +* Windows Phone 7 和 8 +* Windows 8 + +### 快速的示例 + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + }); + + // Record audio + mediaRec.startRecord(); + } + + +### Android 的怪癖 + +* Android 設備音訊格式記錄的自我調整多速率。指定的檔應以*.amr*副檔名結尾。 + +### iOS 的怪癖 + +* iOS 只記錄到檔的類型*.wav*和返回一個錯誤如果檔副檔名不正確。 + +* 如果未提供的完整路徑,錄音放在應用程式的 `documents/tmp` 目錄。 這可以通過訪問 `File` API 使用 `LocalFileSystem.TEMPORARY` 。 在記錄時指定的任何子目錄中必須已經存在。 + +* 檔可以記錄和演奏的後面使用的檔的 URI: + + var myMedia = new Media("documents://beer.mp3") + + +### Windows 8 的怪癖 + +* 如果沒有提供完整的路徑,錄音被放在應用程式/temp 目錄。這可以通過訪問 `檔` API 使用 `LocalFileSystem.TEMPORARY` 或 ' ms appdata: temp / / /<filename>' URI。 + +* 在記錄時指定的任何子目錄中必須已經存在。 + +### 泰怪癖 + +* 不支援在 Tizen 設備上。 + +## media.stop + +停止播放音訊檔。 + + media.stop() ; + + +### 簡單的例子 + + // Play audio + // + function playAudio(url) { + // Play the audio file at url + var my_media = new Media(url, + // success callback + function() { + console.log("playAudio():Audio Success"); + }, + // error callback + function(err) { + console.log("playAudio():Audio Error: "+err); + } + ); + + // Play audio + my_media.play(); + + // Pause after 10 seconds + setTimeout(function() { + my_media.stop(); + }, 10000); + } + + +## media.stopRecord + +停止錄製音訊檔。 + + media.stopRecord() ; + + +### 支援的平臺 + +* 安卓系統 +* iOS +* Windows Phone 7 和 8 +* Windows 8 + +### 簡單的例子 + + // Record audio + // + function recordAudio() { + var src = "myrecording.mp3"; + var mediaRec = new Media(src, + // success callback + function() { + console.log("recordAudio():Audio Success"); + }, + + // error callback + function(err) { + console.log("recordAudio():Audio Error: "+ err.code); + } + ); + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 seconds + setTimeout(function() { + mediaRec.stopRecord(); + }, 10000); + } + + +### 泰怪癖 + +* 不支援在 Tizen 設備上。 + +## MediaError + +A `MediaError` 物件返回到 `mediaError` 時出現錯誤的回呼函數。 + +### 屬性 + +* **代碼**: 下面列出的預定義的錯誤代碼之一。 + +* **消息**: 錯誤訊息,描述該錯誤的詳細資訊。 + +### 常量 + +* `MediaError.MEDIA_ERR_ABORTED`= 1 +* `MediaError.MEDIA_ERR_NETWORK`= 2 +* `MediaError.MEDIA_ERR_DECODE`= 3 +* `MediaError.MEDIA_ERR_NONE_SUPPORTED`= 4
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/package.json b/plugins/org.apache.cordova.media/package.json new file mode 100644 index 00000000..a94642a1 --- /dev/null +++ b/plugins/org.apache.cordova.media/package.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.16", + "name": "org.apache.cordova.media", + "cordova_name": "Media", + "description": "Cordova Media Plugin", + "license": "Apache 2.0", + "repo": "https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git", + "issue": "https://issues.apache.org/jira/browse/CB/component/12320647", + "keywords": [ + "cordova", + "media" + ], + "platforms": [ + "android", + "amazon-fireos", + "ubuntu", + "ios", + "blackberry10", + "wp7", + "wp8", + "windows8", + "tizen" + ], + "engines": [], + "englishdoc": "<!---\n Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements. See the NOTICE file\n distributed with this work for additional information\n regarding copyright ownership. The ASF licenses this file\n to you under the Apache License, Version 2.0 (the\n \"License\"); you may not use this file except in compliance\n with the License. You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing,\n software distributed under the License is distributed on an\n \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n KIND, either express or implied. See the License for the\n specific language governing permissions and limitations\n under the License.\n-->\n\n# org.apache.cordova.media\n\nThis plugin provides the ability to record and play back audio files on a device.\n\n__NOTE__: The current implementation does not adhere to a W3C\nspecification for media capture, and is provided for convenience only.\nA future implementation will adhere to the latest W3C specification\nand may deprecate the current APIs.\n\nThis plugin defines a global `Media` Constructor.\n\nAlthough in the global scope, it is not available until after the `deviceready` event.\n\n document.addEventListener(\"deviceready\", onDeviceReady, false);\n function onDeviceReady() {\n console.log(Media);\n }\n\n## Installation\n\n cordova plugin add org.apache.cordova.media\n\n## Supported Platforms\n\n- Android\n- BlackBerry 10\n- iOS\n- Windows Phone 7 and 8\n- Tizen\n- Windows 8\n\n## Windows Phone Quirks\n\n- Only one media file can be played back at a time.\n\n- There are strict restrictions on how your application interacts with other media. See the [Microsoft documentation for details][url].\n\n[url]: http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh184838(v=vs.92).aspx\n\n## Media\n\n var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]);\n\n### Parameters\n\n- __src__: A URI containing the audio content. _(DOMString)_\n\n- __mediaSuccess__: (Optional) The callback that executes after a `Media` object has completed the current play, record, or stop action. _(Function)_\n\n- __mediaError__: (Optional) The callback that executes if an error occurs. _(Function)_\n\n- __mediaStatus__: (Optional) The callback that executes to indicate status changes. _(Function)_\n\n### Constants\n\nThe following constants are reported as the only parameter to the\n`mediaStatus` callback:\n\n- `Media.MEDIA_NONE` = 0;\n- `Media.MEDIA_STARTING` = 1;\n- `Media.MEDIA_RUNNING` = 2;\n- `Media.MEDIA_PAUSED` = 3;\n- `Media.MEDIA_STOPPED` = 4;\n\n### Methods\n\n- `media.getCurrentPosition`: Returns the current position within an audio file.\n\n- `media.getDuration`: Returns the duration of an audio file.\n\n- `media.play`: Start or resume playing an audio file.\n\n- `media.pause`: Pause playback of an audio file.\n\n- `media.release`: Releases the underlying operating system's audio resources.\n\n- `media.seekTo`: Moves the position within the audio file.\n\n- `media.setVolume`: Set the volume for audio playback.\n\n- `media.startRecord`: Start recording an audio file.\n\n- `media.stopRecord`: Stop recording an audio file.\n\n- `media.stop`: Stop playing an audio file.\n\n### Additional ReadOnly Parameters\n\n- __position__: The position within the audio playback, in seconds.\n - Not automatically updated during play; call `getCurrentPosition` to update.\n\n- __duration__: The duration of the media, in seconds.\n\n\n## media.getCurrentPosition\n\nReturns the current position within an audio file. Also updates the `Media` object's `position` parameter.\n\n media.getCurrentPosition(mediaSuccess, [mediaError]);\n\n### Parameters\n\n- __mediaSuccess__: The callback that is passed the current position in seconds.\n\n- __mediaError__: (Optional) The callback to execute if an error occurs.\n\n### Quick Example\n\n // Audio player\n //\n var my_media = new Media(src, onSuccess, onError);\n\n // Update media position every second\n var mediaTimer = setInterval(function () {\n // get media position\n my_media.getCurrentPosition(\n // success callback\n function (position) {\n if (position > -1) {\n console.log((position) + \" sec\");\n }\n },\n // error callback\n function (e) {\n console.log(\"Error getting pos=\" + e);\n }\n );\n }, 1000);\n\n\n## media.getDuration\n\nReturns the duration of an audio file in seconds. If the duration is unknown, it returns a value of -1.\n\n\n media.getDuration();\n\n### Quick Example\n\n // Audio player\n //\n var my_media = new Media(src, onSuccess, onError);\n\n // Get duration\n var counter = 0;\n var timerDur = setInterval(function() {\n counter = counter + 100;\n if (counter > 2000) {\n clearInterval(timerDur);\n }\n var dur = my_media.getDuration();\n if (dur > 0) {\n clearInterval(timerDur);\n document.getElementById('audio_duration').innerHTML = (dur) + \" sec\";\n }\n }, 100);\n\n\n## media.pause\n\nPauses playing an audio file.\n\n media.pause();\n\n\n### Quick Example\n\n // Play audio\n //\n function playAudio(url) {\n // Play the audio file at url\n var my_media = new Media(url,\n // success callback\n function () { console.log(\"playAudio():Audio Success\"); },\n // error callback\n function (err) { console.log(\"playAudio():Audio Error: \" + err); }\n );\n\n // Play audio\n my_media.play();\n\n // Pause after 10 seconds\n setTimeout(function () {\n media.pause();\n }, 10000);\n }\n\n\n## media.play\n\nStarts or resumes playing an audio file.\n\n media.play();\n\n\n### Quick Example\n\n // Play audio\n //\n function playAudio(url) {\n // Play the audio file at url\n var my_media = new Media(url,\n // success callback\n function () {\n console.log(\"playAudio():Audio Success\");\n },\n // error callback\n function (err) {\n console.log(\"playAudio():Audio Error: \" + err);\n }\n );\n // Play audio\n my_media.play();\n }\n\n\n### iOS Quirks\n\n- __numberOfLoops__: Pass this option to the `play` method to specify\n the number of times you want the media file to play, e.g.:\n\n var myMedia = new Media(\"http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3\")\n myMedia.play({ numberOfLoops: 2 })\n\n- __playAudioWhenScreenIsLocked__: Pass in this option to the `play`\n method to specify whether you want to allow playback when the screen\n is locked. If set to `true` (the default value), the state of the\n hardware mute button is ignored, e.g.:\n\n var myMedia = new Media(\"http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3\")\n myMedia.play({ playAudioWhenScreenIsLocked : false })\n\n- __order of file search__: When only a file name or simple path is\n provided, iOS searches in the `www` directory for the file, then in\n the application's `documents/tmp` directory:\n\n var myMedia = new Media(\"audio/beer.mp3\")\n myMedia.play() // first looks for file in www/audio/beer.mp3 then in <application>/documents/tmp/audio/beer.mp3\n\n## media.release\n\nReleases the underlying operating system's audio resources.\nThis is particularly important for Android, since there are a finite amount of\nOpenCore instances for media playback. Applications should call the `release`\nfunction for any `Media` resource that is no longer needed.\n\n media.release();\n\n\n### Quick Example\n\n // Audio player\n //\n var my_media = new Media(src, onSuccess, onError);\n\n my_media.play();\n my_media.stop();\n my_media.release();\n\n\n## media.seekTo\n\nSets the current position within an audio file.\n\n media.seekTo(milliseconds);\n\n### Parameters\n\n- __milliseconds__: The position to set the playback position within the audio, in milliseconds.\n\n\n### Quick Example\n\n // Audio player\n //\n var my_media = new Media(src, onSuccess, onError);\n my_media.play();\n // SeekTo to 10 seconds after 5 seconds\n setTimeout(function() {\n my_media.seekTo(10000);\n }, 5000);\n\n\n### BlackBerry 10 Quirks\n\n- Not supported on BlackBerry OS 5 devices.\n\n## media.setVolume\n\nSet the volume for an audio file.\n\n media.setVolume(volume);\n\n### Parameters\n\n- __volume__: The volume to set for playback. The value must be within the range of 0.0 to 1.0.\n\n### Supported Platforms\n\n- Android\n- iOS\n\n### Quick Example\n\n // Play audio\n //\n function playAudio(url) {\n // Play the audio file at url\n var my_media = new Media(url,\n // success callback\n function() {\n console.log(\"playAudio():Audio Success\");\n },\n // error callback\n function(err) {\n console.log(\"playAudio():Audio Error: \"+err);\n });\n\n // Play audio\n my_media.play();\n\n // Mute volume after 2 seconds\n setTimeout(function() {\n my_media.setVolume('0.0');\n }, 2000);\n\n // Set volume to 1.0 after 5 seconds\n setTimeout(function() {\n my_media.setVolume('1.0');\n }, 5000);\n }\n\n\n## media.startRecord\n\nStarts recording an audio file.\n\n media.startRecord();\n\n### Supported Platforms\n\n- Android\n- iOS\n- Windows Phone 7 and 8\n- Windows 8\n\n### Quick Example\n\n // Record audio\n //\n function recordAudio() {\n var src = \"myrecording.mp3\";\n var mediaRec = new Media(src,\n // success callback\n function() {\n console.log(\"recordAudio():Audio Success\");\n },\n\n // error callback\n function(err) {\n console.log(\"recordAudio():Audio Error: \"+ err.code);\n });\n\n // Record audio\n mediaRec.startRecord();\n }\n\n\n### Android Quirks\n\n- Android devices record audio in Adaptive Multi-Rate format. The specified file should end with a _.amr_ extension.\n- The hardware volume controls are wired up to the media volume while any Media objects are alive. Once the last created Media object has `release()` called on it, the volume controls revert to their default behaviour. The controls are also reset on page navigation, as this releases all Media objects.\n\n### iOS Quirks\n\n- iOS only records to files of type _.wav_ and returns an error if the file name extension is not correct.\n\n- If a full path is not provided, the recording is placed in the application's `documents/tmp` directory. This can be accessed via the `File` API using `LocalFileSystem.TEMPORARY`. Any subdirectory specified at record time must already exist.\n\n- Files can be recorded and played back using the documents URI:\n\n var myMedia = new Media(\"documents://beer.mp3\")\n\n### Windows 8 Quirks\n\n- If a full path is not provided, the recording is placed in the AppData/temp directory. This can be accessed via the `File` API using `LocalFileSystem.TEMPORARY` or 'ms-appdata:///temp/<filename>' URI.\n\n- Any subdirectory specified at record time must already exist.\n\n### Tizen Quirks\n\n- Not supported on Tizen devices.\n\n## media.stop\n\nStops playing an audio file.\n\n media.stop();\n\n### Quick Example\n\n // Play audio\n //\n function playAudio(url) {\n // Play the audio file at url\n var my_media = new Media(url,\n // success callback\n function() {\n console.log(\"playAudio():Audio Success\");\n },\n // error callback\n function(err) {\n console.log(\"playAudio():Audio Error: \"+err);\n }\n );\n\n // Play audio\n my_media.play();\n\n // Pause after 10 seconds\n setTimeout(function() {\n my_media.stop();\n }, 10000);\n }\n\n\n## media.stopRecord\n\nStops recording an audio file.\n\n media.stopRecord();\n\n### Supported Platforms\n\n- Android\n- iOS\n- Windows Phone 7 and 8\n- Windows 8\n\n### Quick Example\n\n // Record audio\n //\n function recordAudio() {\n var src = \"myrecording.mp3\";\n var mediaRec = new Media(src,\n // success callback\n function() {\n console.log(\"recordAudio():Audio Success\");\n },\n\n // error callback\n function(err) {\n console.log(\"recordAudio():Audio Error: \"+ err.code);\n }\n );\n\n // Record audio\n mediaRec.startRecord();\n\n // Stop recording after 10 seconds\n setTimeout(function() {\n mediaRec.stopRecord();\n }, 10000);\n }\n\n\n### Tizen Quirks\n\n- Not supported on Tizen devices.\n\n## MediaError\n\nA `MediaError` object is returned to the `mediaError` callback\nfunction when an error occurs.\n\n### Properties\n\n- __code__: One of the predefined error codes listed below.\n\n- __message__: An error message describing the details of the error.\n\n### Constants\n\n- `MediaError.MEDIA_ERR_ABORTED` = 1\n- `MediaError.MEDIA_ERR_NETWORK` = 2\n- `MediaError.MEDIA_ERR_DECODE` = 3\n- `MediaError.MEDIA_ERR_NONE_SUPPORTED` = 4\n\n" +}
\ No newline at end of file diff --git a/plugins/org.apache.cordova.media/plugin.xml b/plugins/org.apache.cordova.media/plugin.xml new file mode 100644 index 00000000..676665de --- /dev/null +++ b/plugins/org.apache.cordova.media/plugin.xml @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" +xmlns:android="http://schemas.android.com/apk/res/android" +id="org.apache.cordova.media" + version="0.2.16"> + + <name>Media</name> + <description>Cordova Media Plugin</description> + <license>Apache 2.0</license> + <keywords>cordova,media</keywords> + <repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git</repo> + <issue>https://issues.apache.org/jira/browse/CB/component/12320647</issue> + + <dependency id="org.apache.cordova.file" version=">=1.0.1" /> + + <js-module src="www/MediaError.js" name="MediaError"> + <clobbers target="window.MediaError" /> + </js-module> + + <js-module src="www/Media.js" name="Media"> + <clobbers target="window.Media" /> + </js-module> + + <!-- android --> + <platform name="android"> + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="Media" > + <param name="android-package" value="org.apache.cordova.media.AudioHandler"/> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/*"> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + </config-file> + + <source-file src="src/android/AudioHandler.java" target-dir="src/org/apache/cordova/media" /> + <source-file src="src/android/AudioPlayer.java" target-dir="src/org/apache/cordova/media" /> + <source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/media" /> + </platform> + + <!-- amazon-fireos --> + <platform name="amazon-fireos"> + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="Media" > + <param name="android-package" value="org.apache.cordova.media.AudioHandler"/> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/*"> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + </config-file> + + <source-file src="src/android/AudioHandler.java" target-dir="src/org/apache/cordova/media" /> + <source-file src="src/android/AudioPlayer.java" target-dir="src/org/apache/cordova/media" /> + <source-file src="src/android/FileHelper.java" target-dir="src/org/apache/cordova/media" /> + </platform> + + + <!-- ubuntu --> + <platform name="ubuntu"> + <config-file target="config.xml" parent="/*"> + <feature name="Media"> + <param policy_group="microphone" policy_version="1" /> + <param policy_group="video" policy_version="1" /> + </feature> + </config-file> + <header-file src="src/ubuntu/media.h" /> + <source-file src="src/ubuntu/media.cpp" /> + </platform> + + <!-- ios --> + <platform name="ios"> + <config-file target="config.xml" parent="/*"> + <feature name="Media"> + <param name="ios-package" value="CDVSound" /> + </feature> + </config-file> + <header-file src="src/ios/CDVSound.h" /> + <source-file src="src/ios/CDVSound.m" /> + </platform> + + <!-- blackberry10 --> + <platform name="blackberry10"> + <source-file src="src/blackberry10/index.js" target-dir="Media" /> + <config-file target="www/config.xml" parent="/widget"> + <feature name="Media" value="Media"/> + </config-file> + </platform> + + <!-- wp7 --> + <platform name="wp7"> + <config-file target="config.xml" parent="/*"> + <feature name="Media"> + <param name="wp-package" value="Media"/> + </feature> + </config-file> + + <config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Capabilities"> + <Capability Name="ID_CAP_MEDIALIB"/> + <Capability Name="ID_CAP_MICROPHONE"/> + </config-file> + + <source-file src="src/wp/Media.cs" /> + <source-file src="src/wp/AudioPlayer.cs" /> + </platform> + + <!-- wp8 --> + <platform name="wp8"> + <config-file target="config.xml" parent="/*"> + <feature name="Media"> + <param name="wp-package" value="Media"/> + </feature> + </config-file> + + <config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Capabilities"> + <Capability Name="ID_CAP_MEDIALIB_AUDIO"/> + <Capability Name="ID_CAP_MEDIALIB_PLAYBACK"/> + <Capability Name="ID_CAP_MICROPHONE"/> + </config-file> + + <source-file src="src/wp/Media.cs" /> + <source-file src="src/wp/AudioPlayer.cs" /> + </platform> + + <!-- windows8 --> + <platform name="windows8"> + <js-module src="src/windows8/MediaProxy.js" name="MediaProxy"> + <merges target="" /> + </js-module> + + <config-file target="package.appxmanifest" parent="/Package/Capabilities"> + <Capability Name="musicLibrary" /> + <DeviceCapability Name="microphone" /> + </config-file> + </platform> + + <!-- tizen --> + <platform name="tizen"> + <js-module src="src/tizen/MediaProxy.js" name="MediaProxy"> + <runs/> + </js-module> + </platform> +</plugin> diff --git a/plugins/org.apache.cordova.media/src/android/AudioHandler.java b/plugins/org.apache.cordova.media/src/android/AudioHandler.java new file mode 100644 index 00000000..f06b75a4 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/android/AudioHandler.java @@ -0,0 +1,410 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +package org.apache.cordova.media; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaResourceApi; + +import android.content.Context; +import android.media.AudioManager; +import android.net.Uri; +import android.util.Log; + +import java.util.ArrayList; + +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; + +/** + * This class called by CordovaActivity to play and record audio. + * The file can be local or over a network using http. + * + * Audio formats supported (tested): + * .mp3, .wav + * + * Local audio files must reside in one of two places: + * android_asset: file name must start with /android_asset/sound.mp3 + * sdcard: file name is just sound.mp3 + */ +public class AudioHandler extends CordovaPlugin { + + public static String TAG = "AudioHandler"; + HashMap<String, AudioPlayer> players; // Audio player object + ArrayList<AudioPlayer> pausedForPhone; // Audio players that were paused when phone call came in + private int origVolumeStream = -1; + private CallbackContext messageChannel; + + /** + * Constructor. + */ + public AudioHandler() { + this.players = new HashMap<String, AudioPlayer>(); + this.pausedForPhone = new ArrayList<AudioPlayer>(); + } + + /** + * Executes the request and returns PluginResult. + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackContext The callback context used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + CordovaResourceApi resourceApi = webView.getResourceApi(); + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + if (action.equals("startRecordingAudio")) { + String target = args.getString(1); + String fileUriStr; + try { + Uri targetUri = resourceApi.remapUri(Uri.parse(target)); + fileUriStr = targetUri.toString(); + } catch (IllegalArgumentException e) { + fileUriStr = target; + } + this.startRecordingAudio(args.getString(0), FileHelper.stripFileProtocol(fileUriStr)); + } + else if (action.equals("stopRecordingAudio")) { + this.stopRecordingAudio(args.getString(0)); + } + else if (action.equals("startPlayingAudio")) { + String target = args.getString(1); + String fileUriStr; + try { + Uri targetUri = resourceApi.remapUri(Uri.parse(target)); + fileUriStr = targetUri.toString(); + } catch (IllegalArgumentException e) { + fileUriStr = target; + } + this.startPlayingAudio(args.getString(0), FileHelper.stripFileProtocol(fileUriStr)); + } + else if (action.equals("seekToAudio")) { + this.seekToAudio(args.getString(0), args.getInt(1)); + } + else if (action.equals("pausePlayingAudio")) { + this.pausePlayingAudio(args.getString(0)); + } + else if (action.equals("stopPlayingAudio")) { + this.stopPlayingAudio(args.getString(0)); + } else if (action.equals("setVolume")) { + try { + this.setVolume(args.getString(0), Float.parseFloat(args.getString(1))); + } catch (NumberFormatException nfe) { + //no-op + } + } else if (action.equals("getCurrentPositionAudio")) { + float f = this.getCurrentPositionAudio(args.getString(0)); + callbackContext.sendPluginResult(new PluginResult(status, f)); + return true; + } + else if (action.equals("getDurationAudio")) { + float f = this.getDurationAudio(args.getString(0), args.getString(1)); + callbackContext.sendPluginResult(new PluginResult(status, f)); + return true; + } + else if (action.equals("create")) { + String id = args.getString(0); + String src = FileHelper.stripFileProtocol(args.getString(1)); + getOrCreatePlayer(id, src); + } + else if (action.equals("release")) { + boolean b = this.release(args.getString(0)); + callbackContext.sendPluginResult(new PluginResult(status, b)); + return true; + } + else if (action.equals("messageChannel")) { + messageChannel = callbackContext; + return true; + } + else { // Unrecognized action. + return false; + } + + callbackContext.sendPluginResult(new PluginResult(status, result)); + + return true; + } + + /** + * Stop all audio players and recorders. + */ + public void onDestroy() { + if (!players.isEmpty()) { + onLastPlayerReleased(); + } + for (AudioPlayer audio : this.players.values()) { + audio.destroy(); + } + this.players.clear(); + } + + /** + * Stop all audio players and recorders on navigate. + */ + @Override + public void onReset() { + onDestroy(); + } + + /** + * Called when a message is sent to plugin. + * + * @param id The message id + * @param data The message data + * @return Object to stop propagation or null + */ + public Object onMessage(String id, Object data) { + + // If phone message + if (id.equals("telephone")) { + + // If phone ringing, then pause playing + if ("ringing".equals(data) || "offhook".equals(data)) { + + // Get all audio players and pause them + for (AudioPlayer audio : this.players.values()) { + if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) { + this.pausedForPhone.add(audio); + audio.pausePlaying(); + } + } + + } + + // If phone idle, then resume playing those players we paused + else if ("idle".equals(data)) { + for (AudioPlayer audio : this.pausedForPhone) { + audio.startPlaying(null); + } + this.pausedForPhone.clear(); + } + } + return null; + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + private AudioPlayer getOrCreatePlayer(String id, String file) { + AudioPlayer ret = players.get(id); + if (ret == null) { + if (players.isEmpty()) { + onFirstPlayerCreated(); + } + ret = new AudioPlayer(this, id, file); + players.put(id, ret); + } + return ret; + } + + /** + * Release the audio player instance to save memory. + * @param id The id of the audio player + */ + private boolean release(String id) { + AudioPlayer audio = players.remove(id); + if (audio == null) { + return false; + } + if (players.isEmpty()) { + onLastPlayerReleased(); + } + audio.destroy(); + return true; + } + + /** + * Start recording and save the specified file. + * @param id The id of the audio player + * @param file The name of the file + */ + public void startRecordingAudio(String id, String file) { + AudioPlayer audio = getOrCreatePlayer(id, file); + audio.startRecording(file); + } + + /** + * Stop recording and save to the file specified when recording started. + * @param id The id of the audio player + */ + public void stopRecordingAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.stopRecording(); + } + } + + /** + * Start or resume playing audio file. + * @param id The id of the audio player + * @param file The name of the audio file. + */ + public void startPlayingAudio(String id, String file) { + AudioPlayer audio = getOrCreatePlayer(id, file); + audio.startPlaying(file); + } + + /** + * Seek to a location. + * @param id The id of the audio player + * @param milliseconds int: number of milliseconds to skip 1000 = 1 second + */ + public void seekToAudio(String id, int milliseconds) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.seekToPlaying(milliseconds); + } + } + + /** + * Pause playing. + * @param id The id of the audio player + */ + public void pausePlayingAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.pausePlaying(); + } + } + + /** + * Stop playing the audio file. + * @param id The id of the audio player + */ + public void stopPlayingAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.stopPlaying(); + } + } + + /** + * Get current position of playback. + * @param id The id of the audio player + * @return position in msec + */ + public float getCurrentPositionAudio(String id) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + return (audio.getCurrentPosition() / 1000.0f); + } + return -1; + } + + /** + * Get the duration of the audio file. + * @param id The id of the audio player + * @param file The name of the audio file. + * @return The duration in msec. + */ + public float getDurationAudio(String id, String file) { + AudioPlayer audio = getOrCreatePlayer(id, file); + return audio.getDuration(file); + } + + /** + * Set the audio device to be used for playback. + * + * @param output 1=earpiece, 2=speaker + */ + @SuppressWarnings("deprecation") + public void setAudioOutputDevice(int output) { + AudioManager audiMgr = (AudioManager) this.cordova.getActivity().getSystemService(Context.AUDIO_SERVICE); + if (output == 2) { + audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL); + } + else if (output == 1) { + audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL); + } + else { + System.out.println("AudioHandler.setAudioOutputDevice() Error: Unknown output device."); + } + } + + /** + * Get the audio device to be used for playback. + * + * @return 1=earpiece, 2=speaker + */ + @SuppressWarnings("deprecation") + public int getAudioOutputDevice() { + AudioManager audiMgr = (AudioManager) this.cordova.getActivity().getSystemService(Context.AUDIO_SERVICE); + if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) { + return 1; + } + else if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_SPEAKER) { + return 2; + } + else { + return -1; + } + } + + /** + * Set the volume for an audio device + * + * @param id The id of the audio player + * @param volume Volume to adjust to 0.0f - 1.0f + */ + public void setVolume(String id, float volume) { + AudioPlayer audio = this.players.get(id); + if (audio != null) { + audio.setVolume(volume); + } else { + System.out.println("AudioHandler.setVolume() Error: Unknown Audio Player " + id); + } + } + + private void onFirstPlayerCreated() { + origVolumeStream = cordova.getActivity().getVolumeControlStream(); + cordova.getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC); + } + + private void onLastPlayerReleased() { + if (origVolumeStream != -1) { + cordova.getActivity().setVolumeControlStream(origVolumeStream); + origVolumeStream = -1; + } + } + + void sendEventMessage(String action, JSONObject actionData) { + JSONObject message = new JSONObject(); + try { + message.put("action", action); + if (actionData != null) { + message.put(action, actionData); + } + } catch (JSONException e) { + Log.e(TAG, "Failed to create event message", e); + } + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, message); + pluginResult.setKeepCallback(true); + if (messageChannel != null) { + messageChannel.sendPluginResult(pluginResult); + } + } +} diff --git a/plugins/org.apache.cordova.media/src/android/AudioPlayer.java b/plugins/org.apache.cordova.media/src/android/AudioPlayer.java new file mode 100644 index 00000000..7524c5b6 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/android/AudioPlayer.java @@ -0,0 +1,587 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +package org.apache.cordova.media; + +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.media.MediaRecorder; +import android.os.Environment; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * This class implements the audio playback and recording capabilities used by Cordova. + * It is called by the AudioHandler Cordova class. + * Only one file can be played or recorded per class instance. + * + * Local audio files must reside in one of two places: + * android_asset: file name must start with /android_asset/sound.mp3 + * sdcard: file name is just sound.mp3 + */ +public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { + + // AudioPlayer modes + public enum MODE { NONE, PLAY, RECORD }; + + // AudioPlayer states + public enum STATE { MEDIA_NONE, + MEDIA_STARTING, + MEDIA_RUNNING, + MEDIA_PAUSED, + MEDIA_STOPPED, + MEDIA_LOADING + }; + + private static final String LOG_TAG = "AudioPlayer"; + + // AudioPlayer message ids + private static int MEDIA_STATE = 1; + private static int MEDIA_DURATION = 2; + private static int MEDIA_POSITION = 3; + private static int MEDIA_ERROR = 9; + + // Media error codes + private static int MEDIA_ERR_NONE_ACTIVE = 0; + private static int MEDIA_ERR_ABORTED = 1; +// private static int MEDIA_ERR_NETWORK = 2; +// private static int MEDIA_ERR_DECODE = 3; +// private static int MEDIA_ERR_NONE_SUPPORTED = 4; + + private AudioHandler handler; // The AudioHandler object + private String id; // The id of this player (used to identify Media object in JavaScript) + private MODE mode = MODE.NONE; // Playback or Recording mode + private STATE state = STATE.MEDIA_NONE; // State of recording or playback + + private String audioFile = null; // File name to play or record to + private float duration = -1; // Duration of audio + + private MediaRecorder recorder = null; // Audio recording object + private String tempFile = null; // Temporary recording file name + + private MediaPlayer player = null; // Audio player object + private boolean prepareOnly = true; // playback after file prepare flag + private int seekOnPrepared = 0; // seek to this location once media is prepared + + /** + * Constructor. + * + * @param handler The audio handler object + * @param id The id of this audio player + */ + public AudioPlayer(AudioHandler handler, String id, String file) { + this.handler = handler; + this.id = id; + this.audioFile = file; + this.recorder = new MediaRecorder(); + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.3gp"; + } else { + this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.3gp"; + } + + } + + /** + * Destroy player and stop audio playing or recording. + */ + public void destroy() { + // Stop any play or record + if (this.player != null) { + if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { + this.player.stop(); + this.setState(STATE.MEDIA_STOPPED); + } + this.player.release(); + this.player = null; + } + if (this.recorder != null) { + this.stopRecording(); + this.recorder.release(); + this.recorder = null; + } + } + + /** + * Start recording the specified file. + * + * @param file The name of the file + */ + public void startRecording(String file) { + switch (this.mode) { + case PLAY: + Log.d(LOG_TAG, "AudioPlayer Error: Can't record in play mode."); + sendErrorStatus(MEDIA_ERR_ABORTED); + break; + case NONE: + this.audioFile = file; + this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC); + this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP); + this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB); + this.recorder.setOutputFile(this.tempFile); + try { + this.recorder.prepare(); + this.recorder.start(); + this.setState(STATE.MEDIA_RUNNING); + return; + } catch (IllegalStateException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + sendErrorStatus(MEDIA_ERR_ABORTED); + break; + case RECORD: + Log.d(LOG_TAG, "AudioPlayer Error: Already recording."); + sendErrorStatus(MEDIA_ERR_ABORTED); + } + } + + /** + * Save temporary recorded file to specified name + * + * @param file + */ + public void moveFile(String file) { + /* this is a hack to save the file as the specified name */ + File f = new File(this.tempFile); + + if (!file.startsWith("/")) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + file = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + file; + } else { + file = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/" + file; + } + } + + String logMsg = "renaming " + this.tempFile + " to " + file; + Log.d(LOG_TAG, logMsg); + if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg); + } + + /** + * Stop recording and save to the file specified when recording started. + */ + public void stopRecording() { + if (this.recorder != null) { + try{ + if (this.state == STATE.MEDIA_RUNNING) { + this.recorder.stop(); + this.setState(STATE.MEDIA_STOPPED); + } + this.recorder.reset(); + this.moveFile(this.audioFile); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + + //========================================================================== + // Playback + //========================================================================== + + /** + * Start or resume playing audio file. + * + * @param file The name of the audio file. + */ + public void startPlaying(String file) { + if (this.readyPlayer(file) && this.player != null) { + this.player.start(); + this.setState(STATE.MEDIA_RUNNING); + this.seekOnPrepared = 0; //insures this is always reset + } else { + this.prepareOnly = false; + } + } + + /** + * Seek or jump to a new time in the track. + */ + public void seekToPlaying(int milliseconds) { + if (this.readyPlayer(this.audioFile)) { + this.player.seekTo(milliseconds); + Log.d(LOG_TAG, "Send a onStatus update for the new seek"); + sendStatusChange(MEDIA_POSITION, null, (milliseconds / 1000.0f)); + } + else { + this.seekOnPrepared = milliseconds; + } + } + + /** + * Pause playing. + */ + public void pausePlaying() { + + // If playing, then pause + if (this.state == STATE.MEDIA_RUNNING && this.player != null) { + this.player.pause(); + this.setState(STATE.MEDIA_PAUSED); + } + else { + Log.d(LOG_TAG, "AudioPlayer Error: pausePlaying() called during invalid state: " + this.state.ordinal()); + sendErrorStatus(MEDIA_ERR_NONE_ACTIVE); + } + } + + /** + * Stop playing the audio file. + */ + public void stopPlaying() { + if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { + this.player.pause(); + this.player.seekTo(0); + Log.d(LOG_TAG, "stopPlaying is calling stopped"); + this.setState(STATE.MEDIA_STOPPED); + } + else { + Log.d(LOG_TAG, "AudioPlayer Error: stopPlaying() called during invalid state: " + this.state.ordinal()); + sendErrorStatus(MEDIA_ERR_NONE_ACTIVE); + } + } + + /** + * Callback to be invoked when playback of a media source has completed. + * + * @param player The MediaPlayer that reached the end of the file + */ + public void onCompletion(MediaPlayer player) { + Log.d(LOG_TAG, "on completion is calling stopped"); + this.setState(STATE.MEDIA_STOPPED); + } + + /** + * Get current position of playback. + * + * @return position in msec or -1 if not playing + */ + public long getCurrentPosition() { + if ((this.state == STATE.MEDIA_RUNNING) || (this.state == STATE.MEDIA_PAUSED)) { + int curPos = this.player.getCurrentPosition(); + sendStatusChange(MEDIA_POSITION, null, (curPos / 1000.0f)); + return curPos; + } + else { + return -1; + } + } + + /** + * Determine if playback file is streaming or local. + * It is streaming if file name starts with "http://" + * + * @param file The file name + * @return T=streaming, F=local + */ + public boolean isStreaming(String file) { + if (file.contains("http://") || file.contains("https://")) { + return true; + } + else { + return false; + } + } + + /** + * Get the duration of the audio file. + * + * @param file The name of the audio file. + * @return The duration in msec. + * -1=can't be determined + * -2=not allowed + */ + public float getDuration(String file) { + + // Can't get duration of recording + if (this.recorder != null) { + return (-2); // not allowed + } + + // If audio file already loaded and started, then return duration + if (this.player != null) { + return this.duration; + } + + // If no player yet, then create one + else { + this.prepareOnly = true; + this.startPlaying(file); + + // This will only return value for local, since streaming + // file hasn't been read yet. + return this.duration; + } + } + + /** + * Callback to be invoked when the media source is ready for playback. + * + * @param player The MediaPlayer that is ready for playback + */ + public void onPrepared(MediaPlayer player) { + // Listen for playback completion + this.player.setOnCompletionListener(this); + // seek to any location received while not prepared + this.seekToPlaying(this.seekOnPrepared); + // If start playing after prepared + if (!this.prepareOnly) { + this.player.start(); + this.setState(STATE.MEDIA_RUNNING); + this.seekOnPrepared = 0; //reset only when played + } else { + this.setState(STATE.MEDIA_STARTING); + } + // Save off duration + this.duration = getDurationInSeconds(); + // reset prepare only flag + this.prepareOnly = true; + + // Send status notification to JavaScript + sendStatusChange(MEDIA_DURATION, null, this.duration); + } + + /** + * By default Android returns the length of audio in mills but we want seconds + * + * @return length of clip in seconds + */ + private float getDurationInSeconds() { + return (this.player.getDuration() / 1000.0f); + } + + /** + * Callback to be invoked when there has been an error during an asynchronous operation + * (other errors will throw exceptions at method call time). + * + * @param player the MediaPlayer the error pertains to + * @param arg1 the type of error that has occurred: (MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_SERVER_DIED) + * @param arg2 an extra code, specific to the error. + */ + public boolean onError(MediaPlayer player, int arg1, int arg2) { + Log.d(LOG_TAG, "AudioPlayer.onError(" + arg1 + ", " + arg2 + ")"); + + // TODO: Not sure if this needs to be sent? + this.player.stop(); + this.player.release(); + + // Send error notification to JavaScript + sendErrorStatus(arg1); + return false; + } + + /** + * Set the state and send it to JavaScript. + * + * @param state + */ + private void setState(STATE state) { + if (this.state != state) { + sendStatusChange(MEDIA_STATE, null, (float)state.ordinal()); + } + this.state = state; + } + + /** + * Set the mode and send it to JavaScript. + * + * @param mode + */ + private void setMode(MODE mode) { + if (this.mode != mode) { + //mode is not part of the expected behavior, so no notification + //this.handler.webView.sendJavascript("cordova.require('org.apache.cordova.media.Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");"); + } + this.mode = mode; + } + + /** + * Get the audio state. + * + * @return int + */ + public int getState() { + return this.state.ordinal(); + } + + /** + * Set the volume for audio player + * + * @param volume + */ + public void setVolume(float volume) { + this.player.setVolume(volume, volume); + } + + /** + * attempts to put the player in play mode + * @return true if in playmode, false otherwise + */ + private boolean playMode() { + switch(this.mode) { + case NONE: + this.setMode(MODE.PLAY); + break; + case PLAY: + break; + case RECORD: + Log.d(LOG_TAG, "AudioPlayer Error: Can't play in record mode."); + sendErrorStatus(MEDIA_ERR_ABORTED); + return false; //player is not ready + } + return true; + } + + /** + * attempts to initialize the media player for playback + * @param file the file to play + * @return false if player not ready, reports if in wrong mode or state + */ + private boolean readyPlayer(String file) { + if (playMode()) { + switch (this.state) { + case MEDIA_NONE: + if (this.player == null) { + this.player = new MediaPlayer(); + } + try { + this.loadAudioFile(file); + } catch (Exception e) { + sendErrorStatus(MEDIA_ERR_ABORTED); + } + return false; + case MEDIA_LOADING: + //cordova js is not aware of MEDIA_LOADING, so we send MEDIA_STARTING instead + Log.d(LOG_TAG, "AudioPlayer Loading: startPlaying() called during media preparation: " + STATE.MEDIA_STARTING.ordinal()); + this.prepareOnly = false; + return false; + case MEDIA_STARTING: + case MEDIA_RUNNING: + case MEDIA_PAUSED: + return true; + case MEDIA_STOPPED: + //if we are readying the same file + if (this.audioFile.compareTo(file) == 0) { + //reset the audio file + player.seekTo(0); + player.pause(); + return true; + } else { + //reset the player + this.player.reset(); + try { + this.loadAudioFile(file); + } catch (Exception e) { + sendErrorStatus(MEDIA_ERR_ABORTED); + } + //if we had to prepare the file, we won't be in the correct state for playback + return false; + } + default: + Log.d(LOG_TAG, "AudioPlayer Error: startPlaying() called during invalid state: " + this.state); + sendErrorStatus(MEDIA_ERR_ABORTED); + } + } + return false; + } + + /** + * load audio file + * @throws IOException + * @throws IllegalStateException + * @throws SecurityException + * @throws IllegalArgumentException + */ + private void loadAudioFile(String file) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException { + if (this.isStreaming(file)) { + this.player.setDataSource(file); + this.player.setAudioStreamType(AudioManager.STREAM_MUSIC); + //if it's a streaming file, play mode is implied + this.setMode(MODE.PLAY); + this.setState(STATE.MEDIA_STARTING); + this.player.setOnPreparedListener(this); + this.player.prepareAsync(); + } + else { + if (file.startsWith("/android_asset/")) { + String f = file.substring(15); + android.content.res.AssetFileDescriptor fd = this.handler.cordova.getActivity().getAssets().openFd(f); + this.player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); + } + else { + File fp = new File(file); + if (fp.exists()) { + FileInputStream fileInputStream = new FileInputStream(file); + this.player.setDataSource(fileInputStream.getFD()); + fileInputStream.close(); + } + else { + this.player.setDataSource(Environment.getExternalStorageDirectory().getPath() + "/" + file); + } + } + this.setState(STATE.MEDIA_STARTING); + this.player.setOnPreparedListener(this); + this.player.prepare(); + + // Get duration + this.duration = getDurationInSeconds(); + } + } + + private void sendErrorStatus(int errorCode) { + sendStatusChange(MEDIA_ERROR, errorCode, null); + } + + private void sendStatusChange(int messageType, Integer additionalCode, Float value) { + + if (additionalCode != null && value != null) { + throw new IllegalArgumentException("Only one of additionalCode or value can be specified, not both"); + } + + JSONObject statusDetails = new JSONObject(); + try { + statusDetails.put("id", this.id); + statusDetails.put("msgType", messageType); + if (additionalCode != null) { + JSONObject code = new JSONObject(); + code.put("code", additionalCode.intValue()); + statusDetails.put("value", code); + } + else if (value != null) { + statusDetails.put("value", value.floatValue()); + } + } catch (JSONException e) { + Log.e(LOG_TAG, "Failed to create status details", e); + } + + this.handler.sendEventMessage("status", statusDetails); + } +} diff --git a/plugins/org.apache.cordova.media/src/android/FileHelper.java b/plugins/org.apache.cordova.media/src/android/FileHelper.java new file mode 100644 index 00000000..e20752c6 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/android/FileHelper.java @@ -0,0 +1,38 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.cordova.media; + +import android.net.Uri; + +public class FileHelper { + + /** + * Removes the "file://" prefix from the given URI string, if applicable. + * If the given URI string doesn't have a "file://" prefix, it is returned unchanged. + * + * @param uriString the URI string to operate on + * @return a path without the "file://" prefix + */ + public static String stripFileProtocol(String uriString) { + if (uriString.startsWith("file://")) { + return Uri.parse(uriString).getPath(); + } + return uriString; + } +} diff --git a/plugins/org.apache.cordova.media/src/blackberry10/index.js b/plugins/org.apache.cordova.media/src/blackberry10/index.js new file mode 100644 index 00000000..1b9b7860 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/blackberry10/index.js @@ -0,0 +1,237 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var audioObjects = {}, + mediaErrorsHandled = false; + +// There is a bug in the webplatform handling of media error +// dialogs prior to 10.2. This function needs to be run once +// on the webview which plays audio to prevent freezing. +function handleMediaErrors() { + var webview = qnx.webplatform.getWebViews()[0], + handler = webview.onDialogRequested; + if (!mediaErrorsHandled) { + webview.allowWebEvent("DialogRequested"); + webview.onDialogRequested = undefined; + webview.onDialogRequested = function (eventArgs) { + var parsedArgs = JSON.parse(eventArgs); + if (parsedArgs.dialogType === 'MediaError') { + return '{"setPreventDefault": true}'; + } + handler(eventArgs); + }; + mediaErrorsHandled = true; + } +} + +module.exports = { + + create: function (success, fail, args, env) { + var result = new PluginResult(args, env), + id; + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + id = JSON.parse(decodeURIComponent(args[0])); + + if (!args[1]){ + audioObjects[id] = new Audio(); + } else { + audioObjects[id] = new Audio(JSON.parse(decodeURIComponent(args[1]))); + } + + handleMediaErrors(); + + result.ok(); + }, + + startPlayingAudio: function (success, fail, args, env) { + + var audio, + id, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + id = JSON.parse(decodeURIComponent(args[0])); + + audio = audioObjects[id]; + + if (!audio) { + result.error("Audio object has not been initialized"); + } else { + audio.play(); + result.ok(); + } + }, + + stopPlayingAudio: function (success, fail, args, env) { + + var audio, + id, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + id = JSON.parse(decodeURIComponent(args[0])); + + audio = audioObjects[id]; + + if (!audio) { + result.error("Audio Object has not been initialized"); + return; + } + + audio.pause(); + audio.currentTime = 0; + + result.ok(); + }, + + seekToAudio: function (success, fail, args, env) { + + var audio, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))]; + + if (!audio) { + result.error("Audio Object has not been initialized"); + } else if (!args[1]) { + result.error("Media seek time argument not found"); + } else { + try { + audio.currentTime = JSON.parse(decodeURIComponent(args[1])) / 1000; + result.ok(); + } catch (e) { + result.error("Error seeking audio: " + e); + } + } + }, + + pausePlayingAudio: function (success, fail, args, env) { + + var audio, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))]; + + if (!audio) { + result.error("Audio Object has not been initialized"); + return; + } + + audio.pause(); + }, + + getCurrentPositionAudio: function (success, fail, args, env) { + + var audio, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))]; + + if (!audio) { + result.error("Audio Object has not been initialized"); + return; + } + + result.ok(audio.currentTime); + }, + + getDuration: function (success, fail, args, env) { + + var audio, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + audio = audioObjects[JSON.parse(decodeURIComponent(args[0]))]; + + if (!audio) { + result.error("Audio Object has not been initialized"); + return; + } + + result.ok(audio.duration); + }, + + startRecordingAudio: function (success, fail, args, env) { + var result = new PluginResult(args, env); + result.error("Not supported"); + }, + + stopRecordingAudio: function (success, fail, args, env) { + var result = new PluginResult(args, env); + result.error("Not supported"); + }, + + release: function (success, fail, args, env) { + var audio, + id, + result = new PluginResult(args, env); + + if (!args[0]) { + result.error("Media Object id was not sent in arguments"); + return; + } + + id = JSON.parse(decodeURIComponent(args[0])); + + audio = audioObjects[id]; + + if (audio) { + if(audio.src !== ""){ + audio.src = undefined; + } + audioObjects[id] = undefined; + } + + result.ok(); + } +}; diff --git a/plugins/org.apache.cordova.media/src/ios/CDVSound.h b/plugins/org.apache.cordova.media/src/ios/CDVSound.h new file mode 100644 index 00000000..984924de --- /dev/null +++ b/plugins/org.apache.cordova.media/src/ios/CDVSound.h @@ -0,0 +1,113 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import <Foundation/Foundation.h> +#import <AudioToolbox/AudioServices.h> +#import <AVFoundation/AVFoundation.h> + +#import <Cordova/CDVPlugin.h> + +enum CDVMediaError { + MEDIA_ERR_ABORTED = 1, + MEDIA_ERR_NETWORK = 2, + MEDIA_ERR_DECODE = 3, + MEDIA_ERR_NONE_SUPPORTED = 4 +}; +typedef NSUInteger CDVMediaError; + +enum CDVMediaStates { + MEDIA_NONE = 0, + MEDIA_STARTING = 1, + MEDIA_RUNNING = 2, + MEDIA_PAUSED = 3, + MEDIA_STOPPED = 4 +}; +typedef NSUInteger CDVMediaStates; + +enum CDVMediaMsg { + MEDIA_STATE = 1, + MEDIA_DURATION = 2, + MEDIA_POSITION = 3, + MEDIA_ERROR = 9 +}; +typedef NSUInteger CDVMediaMsg; + +@interface CDVAudioPlayer : AVAudioPlayer +{ + NSString* mediaId; +} +@property (nonatomic, copy) NSString* mediaId; +@end + +@interface CDVAudioRecorder : AVAudioRecorder +{ + NSString* mediaId; +} +@property (nonatomic, copy) NSString* mediaId; +@end + +@interface CDVAudioFile : NSObject +{ + NSString* resourcePath; + NSURL* resourceURL; + CDVAudioPlayer* player; + CDVAudioRecorder* recorder; + NSNumber* volume; +} + +@property (nonatomic, strong) NSString* resourcePath; +@property (nonatomic, strong) NSURL* resourceURL; +@property (nonatomic, strong) CDVAudioPlayer* player; +@property (nonatomic, strong) NSNumber* volume; + +@property (nonatomic, strong) CDVAudioRecorder* recorder; + +@end + +@interface CDVSound : CDVPlugin <AVAudioPlayerDelegate, AVAudioRecorderDelegate> +{ + NSMutableDictionary* soundCache; + AVAudioSession* avSession; +} +@property (nonatomic, strong) NSMutableDictionary* soundCache; +@property (nonatomic, strong) AVAudioSession* avSession; + +- (void)startPlayingAudio:(CDVInvokedUrlCommand*)command; +- (void)pausePlayingAudio:(CDVInvokedUrlCommand*)command; +- (void)stopPlayingAudio:(CDVInvokedUrlCommand*)command; +- (void)seekToAudio:(CDVInvokedUrlCommand*)command; +- (void)release:(CDVInvokedUrlCommand*)command; +- (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command; + +- (BOOL)hasAudioSession; + +// helper methods +- (NSURL*)urlForRecording:(NSString*)resourcePath; +- (NSURL*)urlForPlaying:(NSString*)resourcePath; + +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord; +- (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId; +- (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message; + +- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command; +- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command; + +- (void)setVolume:(CDVInvokedUrlCommand*)command; + +@end diff --git a/plugins/org.apache.cordova.media/src/ios/CDVSound.m b/plugins/org.apache.cordova.media/src/ios/CDVSound.m new file mode 100644 index 00000000..309b2e29 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/ios/CDVSound.m @@ -0,0 +1,703 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "CDVSound.h" +#import "CDVFile.h" +#import <Cordova/NSArray+Comparisons.h> + +#define DOCUMENTS_SCHEME_PREFIX @"documents://" +#define HTTP_SCHEME_PREFIX @"http://" +#define HTTPS_SCHEME_PREFIX @"https://" +#define CDVFILE_PREFIX @"cdvfile://" +#define RECORDING_WAV @"wav" + +@implementation CDVSound + +@synthesize soundCache, avSession; + +// Maps a url for a resource path for recording +- (NSURL*)urlForRecording:(NSString*)resourcePath +{ + NSURL* resourceURL = nil; + NSString* filePath = nil; + NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + + // first check for correct extension + if ([[resourcePath pathExtension] caseInsensitiveCompare:RECORDING_WAV] != NSOrderedSame) { + resourceURL = nil; + NSLog(@"Resource for recording must have %@ extension", RECORDING_WAV); + } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { + // try to find Documents:// resources + filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]]; + NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); + } else if ([resourcePath hasPrefix:CDVFILE_PREFIX]) { + CDVFile *filePlugin = [self.commandDelegate getCommandInstance:@"File"]; + CDVFilesystemURL *url = [CDVFilesystemURL fileSystemURLWithString:resourcePath]; + filePath = [filePlugin filesystemPathForURL:url]; + if (filePath == nil) { + resourceURL = [NSURL URLWithString:resourcePath]; + } + } else { + // if resourcePath is not from FileSystem put in tmp dir, else attempt to use provided resource path + NSString* tmpPath = [NSTemporaryDirectory()stringByStandardizingPath]; + BOOL isTmp = [resourcePath rangeOfString:tmpPath].location != NSNotFound; + BOOL isDoc = [resourcePath rangeOfString:docsPath].location != NSNotFound; + if (!isTmp && !isDoc) { + // put in temp dir + filePath = [NSString stringWithFormat:@"%@/%@", tmpPath, resourcePath]; + } else { + filePath = resourcePath; + } + } + + if (filePath != nil) { + // create resourceURL + resourceURL = [NSURL fileURLWithPath:filePath]; + } + return resourceURL; +} + +// Maps a url for a resource path for playing +// "Naked" resource paths are assumed to be from the www folder as its base +- (NSURL*)urlForPlaying:(NSString*)resourcePath +{ + NSURL* resourceURL = nil; + NSString* filePath = nil; + + // first try to find HTTP:// or Documents:// resources + + if ([resourcePath hasPrefix:HTTP_SCHEME_PREFIX] || [resourcePath hasPrefix:HTTPS_SCHEME_PREFIX]) { + // if it is a http url, use it + NSLog(@"Will use resource '%@' from the Internet.", resourcePath); + resourceURL = [NSURL URLWithString:resourcePath]; + } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { + NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]]; + NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); + } else if ([resourcePath hasPrefix:CDVFILE_PREFIX]) { + CDVFile *filePlugin = [self.commandDelegate getCommandInstance:@"File"]; + CDVFilesystemURL *url = [CDVFilesystemURL fileSystemURLWithString:resourcePath]; + filePath = [filePlugin filesystemPathForURL:url]; + if (filePath == nil) { + resourceURL = [NSURL URLWithString:resourcePath]; + } + } else { + // attempt to find file path in www directory or LocalFileSystem.TEMPORARY directory + filePath = [self.commandDelegate pathForResource:resourcePath]; + if (filePath == nil) { + // see if this exists in the documents/temp directory from a previous recording + NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], resourcePath]; + if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) { + // inefficient as existence will be checked again below but only way to determine if file exists from previous recording + filePath = testPath; + NSLog(@"Will attempt to use file resource from LocalFileSystem.TEMPORARY directory"); + } else { + // attempt to use path provided + filePath = resourcePath; + NSLog(@"Will attempt to use file resource '%@'", filePath); + } + } else { + NSLog(@"Found resource '%@' in the web folder.", filePath); + } + } + // if the resourcePath resolved to a file path, check that file exists + if (filePath != nil) { + // create resourceURL + resourceURL = [NSURL fileURLWithPath:filePath]; + // try to access file + NSFileManager* fMgr = [NSFileManager defaultManager]; + if (![fMgr fileExistsAtPath:filePath]) { + resourceURL = nil; + NSLog(@"Unknown resource '%@'", resourcePath); + } + } + + return resourceURL; +} + +// Creates or gets the cached audio file resource object +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord +{ + BOOL bError = NO; + CDVMediaError errcode = MEDIA_ERR_NONE_SUPPORTED; + NSString* errMsg = @""; + NSString* jsString = nil; + CDVAudioFile* audioFile = nil; + NSURL* resourceURL = nil; + + if ([self soundCache] == nil) { + [self setSoundCache:[NSMutableDictionary dictionaryWithCapacity:1]]; + } else { + audioFile = [[self soundCache] objectForKey:mediaId]; + } + if (audioFile == nil) { + // validate resourcePath and create + if ((resourcePath == nil) || ![resourcePath isKindOfClass:[NSString class]] || [resourcePath isEqualToString:@""]) { + bError = YES; + errcode = MEDIA_ERR_ABORTED; + errMsg = @"invalid media src argument"; + } else { + audioFile = [[CDVAudioFile alloc] init]; + audioFile.resourcePath = resourcePath; + audioFile.resourceURL = nil; // validate resourceURL when actually play or record + [[self soundCache] setObject:audioFile forKey:mediaId]; + } + } + if (bValidate && (audioFile.resourceURL == nil)) { + if (bRecord) { + resourceURL = [self urlForRecording:resourcePath]; + } else { + resourceURL = [self urlForPlaying:resourcePath]; + } + if (resourceURL == nil) { + bError = YES; + errcode = MEDIA_ERR_ABORTED; + errMsg = [NSString stringWithFormat:@"Cannot use audio file from resource '%@'", resourcePath]; + } else { + audioFile.resourceURL = resourceURL; + } + } + + if (bError) { + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:errcode message:errMsg]]; + [self.commandDelegate evalJs:jsString]; + } + + return audioFile; +} + +// returns whether or not audioSession is available - creates it if necessary +- (BOOL)hasAudioSession +{ + BOOL bSession = YES; + + if (!self.avSession) { + NSError* error = nil; + + self.avSession = [AVAudioSession sharedInstance]; + if (error) { + // is not fatal if can't get AVAudioSession , just log the error + NSLog(@"error creating audio session: %@", [[error userInfo] description]); + self.avSession = nil; + bSession = NO; + } + } + return bSession; +} + +// helper function to create a error object string +- (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message +{ + NSMutableDictionary* errorDict = [NSMutableDictionary dictionaryWithCapacity:2]; + + [errorDict setObject:[NSNumber numberWithUnsignedInteger:code] forKey:@"code"]; + [errorDict setObject:message ? message:@"" forKey:@"message"]; + + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:errorDict options:0 error:nil]; + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; +} + +- (void)create:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command argumentAtIndex:0]; + NSString* resourcePath = [command argumentAtIndex:1]; + + CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId doValidation:NO forRecording:NO]; + + if (audioFile == nil) { + NSString* errorMessage = [NSString stringWithFormat:@"Failed to initialize Media file with path %@", resourcePath]; + NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMessage]]; + [self.commandDelegate evalJs:jsString]; + } else { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } +} + +- (void)setVolume:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + +#pragma unused(callbackId) + NSString* mediaId = [command argumentAtIndex:0]; + NSNumber* volume = [command argumentAtIndex:1 withDefault:[NSNumber numberWithFloat:1.0]]; + + if ([self soundCache] != nil) { + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + if (audioFile != nil) { + audioFile.volume = volume; + if (audioFile.player) { + audioFile.player.volume = [volume floatValue]; + } + [[self soundCache] setObject:audioFile forKey:mediaId]; + } + } + + // don't care for any callbacks +} + +- (void)startPlayingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + +#pragma unused(callbackId) + NSString* mediaId = [command argumentAtIndex:0]; + NSString* resourcePath = [command argumentAtIndex:1]; + NSDictionary* options = [command argumentAtIndex:2 withDefault:nil]; + + BOOL bError = NO; + NSString* jsString = nil; + + CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId doValidation:YES forRecording:NO]; + if ((audioFile != nil) && (audioFile.resourceURL != nil)) { + if (audioFile.player == nil) { + bError = [self prepareToPlay:audioFile withId:mediaId]; + } + if (!bError) { + // audioFile.player != nil or player was successfully created + // get the audioSession and set the category to allow Playing when device is locked or ring/silent switch engaged + if ([self hasAudioSession]) { + NSError* __autoreleasing err = nil; + NSNumber* playAudioWhenScreenIsLocked = [options objectForKey:@"playAudioWhenScreenIsLocked"]; + BOOL bPlayAudioWhenScreenIsLocked = YES; + if (playAudioWhenScreenIsLocked != nil) { + bPlayAudioWhenScreenIsLocked = [playAudioWhenScreenIsLocked boolValue]; + } + + NSString* sessionCategory = bPlayAudioWhenScreenIsLocked ? AVAudioSessionCategoryPlayback : AVAudioSessionCategorySoloAmbient; + [self.avSession setCategory:sessionCategory error:&err]; + if (![self.avSession setActive:YES error:&err]) { + // other audio with higher priority that does not allow mixing could cause this to fail + NSLog(@"Unable to play audio: %@", [err localizedFailureReason]); + bError = YES; + } + } + if (!bError) { + NSLog(@"Playing audio sample '%@'", audioFile.resourcePath); + NSNumber* loopOption = [options objectForKey:@"numberOfLoops"]; + NSInteger numberOfLoops = 0; + if (loopOption != nil) { + numberOfLoops = [loopOption intValue] - 1; + } + audioFile.player.numberOfLoops = numberOfLoops; + if (audioFile.player.isPlaying) { + [audioFile.player stop]; + audioFile.player.currentTime = 0; + } + if (audioFile.volume != nil) { + audioFile.player.volume = [audioFile.volume floatValue]; + } + + [audioFile.player play]; + double position = round(audioFile.player.duration * 1000) / 1000; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_DURATION, position, @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING]; + [self.commandDelegate evalJs:jsString]; + } + } + if (bError) { + /* I don't see a problem playing previously recorded audio so removing this section - BG + NSError* error; + // try loading it one more time, in case the file was recorded previously + audioFile.player = [[ AVAudioPlayer alloc ] initWithContentsOfURL:audioFile.resourceURL error:&error]; + if (error != nil) { + NSLog(@"Failed to initialize AVAudioPlayer: %@\n", error); + audioFile.player = nil; + } else { + NSLog(@"Playing audio sample '%@'", audioFile.resourcePath); + audioFile.player.numberOfLoops = numberOfLoops; + [audioFile.player play]; + } */ + // error creating the session or player + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_NONE_SUPPORTED]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_NONE_SUPPORTED message:nil]]; + [self.commandDelegate evalJs:jsString]; + } + } + // else audioFile was nil - error already returned from audioFile for resource + return; +} + +- (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId +{ + BOOL bError = NO; + NSError* __autoreleasing playerError = nil; + + // create the player + NSURL* resourceURL = audioFile.resourceURL; + + if ([resourceURL isFileURL]) { + audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:resourceURL error:&playerError]; + } else { + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:resourceURL]; + NSString* userAgent = [self.commandDelegate userAgent]; + if (userAgent) { + [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + + NSURLResponse* __autoreleasing response = nil; + NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&playerError]; + if (playerError) { + NSLog(@"Unable to download audio from: %@", [resourceURL absoluteString]); + } else { + // bug in AVAudioPlayer when playing downloaded data in NSData - we have to download the file and play from disk + CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); + CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); + NSString* filePath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory()stringByStandardizingPath], uuidString]; + CFRelease(uuidString); + CFRelease(uuidRef); + + [data writeToFile:filePath atomically:YES]; + NSURL* fileURL = [NSURL fileURLWithPath:filePath]; + audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&playerError]; + } + } + + if (playerError != nil) { + NSLog(@"Failed to initialize AVAudioPlayer: %@\n", [playerError localizedDescription]); + audioFile.player = nil; + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + bError = YES; + } else { + audioFile.player.mediaId = mediaId; + audioFile.player.delegate = self; + bError = ![audioFile.player prepareToPlay]; + } + return bError; +} + +- (void)stopPlayingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command argumentAtIndex:0]; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if ((audioFile != nil) && (audioFile.player != nil)) { + NSLog(@"Stopped playing audio sample '%@'", audioFile.resourcePath); + [audioFile.player stop]; + audioFile.player.currentTime = 0; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + } // ignore if no media playing + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)pausePlayingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command argumentAtIndex:0]; + NSString* jsString = nil; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + + if ((audioFile != nil) && (audioFile.player != nil)) { + NSLog(@"Paused playing audio sample '%@'", audioFile.resourcePath); + [audioFile.player pause]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_PAUSED]; + } + // ignore if no media playing + + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)seekToAudio:(CDVInvokedUrlCommand*)command +{ + // args: + // 0 = Media id + // 1 = path to resource + // 2 = seek to location in milliseconds + + NSString* mediaId = [command argumentAtIndex:0]; + + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + double position = [[command argumentAtIndex:1] doubleValue]; + + if ((audioFile != nil) && (audioFile.player != nil)) { + NSString* jsString; + double posInSeconds = position / 1000; + if (posInSeconds >= audioFile.player.duration) { + // The seek is past the end of file. Stop media and reset to beginning instead of seeking past the end. + [audioFile.player stop]; + audioFile.player.currentTime = 0; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);\n%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_POSITION, 0.0, @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + // NSLog(@"seekToEndJsString=%@",jsString); + } else { + audioFile.player.currentTime = posInSeconds; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%f);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_POSITION, posInSeconds]; + // NSLog(@"seekJsString=%@",jsString); + } + + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)release:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command argumentAtIndex:0]; + + if (mediaId != nil) { + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + if (audioFile != nil) { + if (audioFile.player && [audioFile.player isPlaying]) { + [audioFile.player stop]; + } + if (audioFile.recorder && [audioFile.recorder isRecording]) { + [audioFile.recorder stop]; + } + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + self.avSession = nil; + } + [[self soundCache] removeObjectForKey:mediaId]; + NSLog(@"Media with id %@ released", mediaId); + } + } +} + +- (void)getCurrentPositionAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + NSString* mediaId = [command argumentAtIndex:0]; + +#pragma unused(mediaId) + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + double position = -1; + + if ((audioFile != nil) && (audioFile.player != nil) && [audioFile.player isPlaying]) { + position = round(audioFile.player.currentTime * 1000) / 1000; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:position]; + + NSString* jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%.3f);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_POSITION, position]; + [self.commandDelegate evalJs:jsString]; + [self.commandDelegate sendPluginResult:result callbackId:callbackId]; +} + +- (void)startRecordingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* callbackId = command.callbackId; + +#pragma unused(callbackId) + + NSString* mediaId = [command argumentAtIndex:0]; + CDVAudioFile* audioFile = [self audioFileForResource:[command argumentAtIndex:1] withId:mediaId doValidation:YES forRecording:YES]; + __block NSString* jsString = nil; + __block NSString* errorMsg = @""; + + if ((audioFile != nil) && (audioFile.resourceURL != nil)) { + void (^startRecording)(void) = ^{ + NSError* __autoreleasing error = nil; + + if (audioFile.recorder != nil) { + [audioFile.recorder stop]; + audioFile.recorder = nil; + } + // get the audioSession and set the category to allow recording when device is locked or ring/silent switch engaged + if ([self hasAudioSession]) { + if (![self.avSession.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) { + [self.avSession setCategory:AVAudioSessionCategoryRecord error:nil]; + } + + if (![self.avSession setActive:YES error:&error]) { + // other audio with higher priority that does not allow mixing could cause this to fail + errorMsg = [NSString stringWithFormat:@"Unable to record audio: %@", [error localizedFailureReason]]; + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_ABORTED]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; + [self.commandDelegate evalJs:jsString]; + return; + } + } + + // create a new recorder for each start record + audioFile.recorder = [[CDVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error]; + + bool recordingSuccess = NO; + if (error == nil) { + audioFile.recorder.delegate = self; + audioFile.recorder.mediaId = mediaId; + recordingSuccess = [audioFile.recorder record]; + if (recordingSuccess) { + NSLog(@"Started recording audio sample '%@'", audioFile.resourcePath); + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_RUNNING]; + [self.commandDelegate evalJs:jsString]; + } + } + + if ((error != nil) || (recordingSuccess == NO)) { + if (error != nil) { + errorMsg = [NSString stringWithFormat:@"Failed to initialize AVAudioRecorder: %@\n", [error localizedFailureReason]]; + } else { + errorMsg = @"Failed to start recording using AVAudioRecorder"; + } + audioFile.recorder = nil; + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; + [self.commandDelegate evalJs:jsString]; + } + }; + + SEL rrpSel = NSSelectorFromString(@"requestRecordPermission:"); + if ([self hasAudioSession] && [self.avSession respondsToSelector:rrpSel]) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.avSession performSelector:rrpSel withObject:^(BOOL granted){ + if (granted) { + startRecording(); + } else { + NSString* msg = @"Error creating audio session, microphone permission denied."; + NSLog(@"%@", msg); + audioFile.recorder = nil; + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:msg]]; + [self.commandDelegate evalJs:jsString]; + } + }]; +#pragma clang diagnostic pop + } else { + startRecording(); + } + + } else { + // file did not validate + NSString* errorMsg = [NSString stringWithFormat:@"Could not record audio at '%@'", audioFile.resourcePath]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)stopRecordingAudio:(CDVInvokedUrlCommand*)command +{ + NSString* mediaId = [command argumentAtIndex:0]; + + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if ((audioFile != nil) && (audioFile.recorder != nil)) { + NSLog(@"Stopped recording audio sample '%@'", audioFile.resourcePath); + [audioFile.recorder stop]; + // no callback - that will happen in audioRecorderDidFinishRecording + } + // ignore if no media recording + if (jsString) { + [self.commandDelegate evalJs:jsString]; + } +} + +- (void)audioRecorderDidFinishRecording:(AVAudioRecorder*)recorder successfully:(BOOL)flag +{ + CDVAudioRecorder* aRecorder = (CDVAudioRecorder*)recorder; + NSString* mediaId = aRecorder.mediaId; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if (audioFile != nil) { + NSLog(@"Finished recording audio sample '%@'", audioFile.resourcePath); + } + if (flag) { + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + } else { + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]]; + } + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + [self.commandDelegate evalJs:jsString]; +} + +- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag +{ + CDVAudioPlayer* aPlayer = (CDVAudioPlayer*)player; + NSString* mediaId = aPlayer.mediaId; + CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId]; + NSString* jsString = nil; + + if (audioFile != nil) { + NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath); + } + if (flag) { + audioFile.player.currentTime = 0; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED]; + } else { + // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('org.apache.cordova.media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]]; + } + if (self.avSession) { + [self.avSession setActive:NO error:nil]; + } + [self.commandDelegate evalJs:jsString]; +} + +- (void)onMemoryWarning +{ + [[self soundCache] removeAllObjects]; + [self setSoundCache:nil]; + [self setAvSession:nil]; + + [super onMemoryWarning]; +} + +- (void)dealloc +{ + [[self soundCache] removeAllObjects]; +} + +- (void)onReset +{ + for (CDVAudioFile* audioFile in [[self soundCache] allValues]) { + if (audioFile != nil) { + if (audioFile.player != nil) { + [audioFile.player stop]; + audioFile.player.currentTime = 0; + } + if (audioFile.recorder != nil) { + [audioFile.recorder stop]; + } + } + } + + [[self soundCache] removeAllObjects]; +} + +@end + +@implementation CDVAudioFile + +@synthesize resourcePath; +@synthesize resourceURL; +@synthesize player, volume; +@synthesize recorder; + +@end +@implementation CDVAudioPlayer +@synthesize mediaId; + +@end + +@implementation CDVAudioRecorder +@synthesize mediaId; + +@end diff --git a/plugins/org.apache.cordova.media/src/tizen/MediaProxy.js b/plugins/org.apache.cordova.media/src/tizen/MediaProxy.js new file mode 100644 index 00000000..c2ee4b07 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/tizen/MediaProxy.js @@ -0,0 +1,223 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var cordova = require('cordova'), + Media = require('org.apache.cordova.media.Media'); + +var MediaError = require('org.apache.cordova.media.MediaError'), + audioObjects = {}; + +module.exports = { + // Initiates the audio file + create:function(successCallback, errorCallback, args) { + var id = args[0], src = args[1]; + + console.log("media::create() - id =" + id + ", src =" + src); + + audioObjects[id] = new Audio(src); + + audioObjects[id].onStalledCB = function () { + console.log("media::onStalled()"); + + audioObjects[id].timer = window.setTimeout( + function () { + audioObjects[id].pause(); + + if (audioObjects[id].currentTime !== 0) + audioObjects[id].currentTime = 0; + + console.log("media::onStalled() - MEDIA_ERROR -> " + MediaError.MEDIA_ERR_ABORTED); + + var err = new MediaError(MediaError.MEDIA_ERR_ABORTED, "Stalled"); + + Media.onStatus(id, Media.MEDIA_ERROR, err); + }, + 2000); + }; + + audioObjects[id].onEndedCB = function () { + console.log("media::onEndedCB() - MEDIA_STATE -> MEDIA_STOPPED"); + + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED); + }; + + audioObjects[id].onErrorCB = function () { + console.log("media::onErrorCB() - MEDIA_ERROR -> " + event.srcElement.error); + + Media.onStatus(id, Media.MEDIA_ERROR, event.srcElement.error); + }; + + audioObjects[id].onPlayCB = function () { + console.log("media::onPlayCB() - MEDIA_STATE -> MEDIA_STARTING"); + + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STARTING); + }; + + audioObjects[id].onPlayingCB = function () { + console.log("media::onPlayingCB() - MEDIA_STATE -> MEDIA_RUNNING"); + + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_RUNNING); + }; + + audioObjects[id].onDurationChangeCB = function () { + console.log("media::onDurationChangeCB() - MEDIA_DURATION -> " + audioObjects[id].duration); + + Media.onStatus(id, Media.MEDIA_DURATION, audioObjects[id].duration); + }; + + audioObjects[id].onTimeUpdateCB = function () { + console.log("media::onTimeUpdateCB() - MEDIA_POSITION -> " + audioObjects[id].currentTime); + + Media.onStatus(id, Media.MEDIA_POSITION, audioObjects[id].currentTime); + }; + + audioObjects[id].onCanPlayCB = function () { + console.log("media::onCanPlayCB()"); + + window.clearTimeout(audioObjects[id].timer); + + audioObjects[id].play(); + }; + + }, + + // Start playing the audio + startPlayingAudio:function(successCallback, errorCallback, args) { + var id = args[0], src = args[1], options = args[2]; + + console.log("media::startPlayingAudio() - id =" + id + ", src =" + src + ", options =" + options); + + audioObjects[id].addEventListener('canplay', audioObjects[id].onCanPlayCB); + audioObjects[id].addEventListener('ended', audioObjects[id].onEndedCB); + audioObjects[id].addEventListener('timeupdate', audioObjects[id].onTimeUpdateCB); + audioObjects[id].addEventListener('durationchange', audioObjects[id].onDurationChangeCB); + audioObjects[id].addEventListener('playing', audioObjects[id].onPlayingCB); + audioObjects[id].addEventListener('play', audioObjects[id].onPlayCB); + audioObjects[id].addEventListener('error', audioObjects[id].onErrorCB); + audioObjects[id].addEventListener('stalled', audioObjects[id].onStalledCB); + + audioObjects[id].play(); + }, + + // Stops the playing audio + stopPlayingAudio:function(successCallback, errorCallback, args) { + var id = args[0]; + + window.clearTimeout(audioObjects[id].timer); + + audioObjects[id].pause(); + + if (audioObjects[id].currentTime !== 0) + audioObjects[id].currentTime = 0; + + console.log("media::stopPlayingAudio() - MEDIA_STATE -> MEDIA_STOPPED"); + + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED); + + audioObjects[id].removeEventListener('canplay', audioObjects[id].onCanPlayCB); + audioObjects[id].removeEventListener('ended', audioObjects[id].onEndedCB); + audioObjects[id].removeEventListener('timeupdate', audioObjects[id].onTimeUpdateCB); + audioObjects[id].removeEventListener('durationchange', audioObjects[id].onDurationChangeCB); + audioObjects[id].removeEventListener('playing', audioObjects[id].onPlayingCB); + audioObjects[id].removeEventListener('play', audioObjects[id].onPlayCB); + audioObjects[id].removeEventListener('error', audioObjects[id].onErrorCB); + audioObjects[id].removeEventListener('error', audioObjects[id].onStalledCB); + }, + + // Seeks to the position in the audio + seekToAudio:function(successCallback, errorCallback, args) { + var id = args[0], milliseconds = args[1]; + + console.log("media::seekToAudio()"); + + audioObjects[id].currentTime = milliseconds; + successCallback( audioObjects[id].currentTime); + }, + + // Pauses the playing audio + pausePlayingAudio:function(successCallback, errorCallback, args) { + var id = args[0]; + + console.log("media::pausePlayingAudio() - MEDIA_STATE -> MEDIA_PAUSED"); + + audioObjects[id].pause(); + + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_PAUSED); + }, + + // Gets current position in the audio + getCurrentPositionAudio:function(successCallback, errorCallback, args) { + var id = args[0]; + console.log("media::getCurrentPositionAudio()"); + successCallback(audioObjects[id].currentTime); + }, + + // Start recording audio + startRecordingAudio:function(successCallback, errorCallback, args) { + var id = args[0], src = args[1]; + + console.log("media::startRecordingAudio() - id =" + id + ", src =" + src); + + function gotStreamCB(stream) { + audioObjects[id].src = webkitURL.createObjectURL(stream); + console.log("media::startRecordingAudio() - stream CB"); + } + + function gotStreamFailedCB(error) { + console.log("media::startRecordingAudio() - error CB:" + error.toString()); + } + + if (navigator.webkitGetUserMedia) { + audioObjects[id] = new Audio(); + navigator.webkitGetUserMedia('audio', gotStreamCB, gotStreamFailedCB); + } else { + console.log("webkitGetUserMedia not supported"); + } + successCallback(); + }, + + // Stop recording audio + stopRecordingAudio:function(successCallback, errorCallback, args) { + var id = args[0]; + + console.log("media::stopRecordingAudio() - id =" + id); + + audioObjects[id].pause(); + successCallback(); + }, + + // Release the media object + release:function(successCallback, errorCallback, args) { + var id = args[0]; + window.clearTimeout(audioObjects[id].timer); + console.log("media::release()"); + }, + + setVolume:function(successCallback, errorCallback, args) { + var id = args[0], volume = args[1]; + + console.log("media::setVolume()"); + + audioObjects[id].volume = volume; + } +}; + +require("cordova/tizen/commandProxy").add("Media", module.exports); diff --git a/plugins/org.apache.cordova.media/src/ubuntu/media.cpp b/plugins/org.apache.cordova.media/src/ubuntu/media.cpp new file mode 100644 index 00000000..2814b5b3 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/ubuntu/media.cpp @@ -0,0 +1,128 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +#include "media.h" + +void Media::create(int scId, int ecId, const QString &id, const QString &src) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) != _id2Player.end()) { + _id2Player[id]->stop(); + _id2Player.remove(id); + } + + _id2Player[id] = QSharedPointer<Player>(new Player(id, src, this)); +} + +void Media::relase(int scId, int ecId, const QString &id) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + _id2Player.remove(id); +} + +void Media::startPlayingAudio(int scId, int ecId, const QString &id, const QString &src, QVariantMap options) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + Q_UNUSED(src); + Q_UNUSED(options); + + if (_id2Player.find(id) == _id2Player.end()) + return; + QSharedPointer<Player> player = _id2Player[id]; + player->play(); +} + +void Media::pausePlayingAudio(int scId, int ecId, const QString &id) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + QSharedPointer<Player> player = _id2Player[id]; + player->pause(); +} + +void Media::stopPlayingAudio(int scId, int ecId, const QString &id) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + QSharedPointer<Player> player = _id2Player[id]; + player->stop(); +} + +void Media::startRecordingAudio(int scId, int ecId, const QString &id, const QString &src) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + Q_UNUSED(src); + + if (_id2Player.find(id) == _id2Player.end()) + return; + QSharedPointer<Player> player = _id2Player[id]; + player->startRecording(); +} + +void Media::stopRecordingAudio(int scId, int ecId, const QString &id) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + QSharedPointer<Player> player = _id2Player[id]; + player->stopRecording(); +} + +void Media::getCurrentPositionAudio(int scId, int ecId, const QString &id) { + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + + QSharedPointer<Player> player = _id2Player[id]; + double position = player->getPosition(); + this->cb(scId, position); +} + +void Media::seekToAudio(int scId, int ecId, const QString &id, qint64 position) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + + QSharedPointer<Player> player = _id2Player[id]; + player->seekTo(position); +} + +void Media::setVolume(int scId, int ecId, const QString &id, int volume) { + Q_UNUSED(scId); + Q_UNUSED(ecId); + + if (_id2Player.find(id) == _id2Player.end()) + return; + QSharedPointer<Player> player = _id2Player[id]; + player->setVolume(volume); +} diff --git a/plugins/org.apache.cordova.media/src/ubuntu/media.h b/plugins/org.apache.cordova.media/src/ubuntu/media.h new file mode 100644 index 00000000..c1f37122 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/ubuntu/media.h @@ -0,0 +1,267 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +#ifndef MEDIA_H_789768978 +#define MEDIA_H_789768978 + +#include <QtMultimedia/QMediaPlayer> +#include <QtCore> +#include <QAudioRecorder> +#include <QtMultimedia/QAudioEncoderSettings> + +#include <cplugin.h> +#include <cordova.h> + +class Player; + +class Media: public CPlugin { + Q_OBJECT +public: + explicit Media(Cordova *cordova): CPlugin(cordova) { + } + + virtual const QString fullName() override { + return Media::fullID(); + } + + virtual const QString shortName() override { + return "Media"; + } + + static const QString fullID() { + return "Media"; + } + + enum State { + MEDIA_NONE = 0, + MEDIA_STARTING = 1, + MEDIA_RUNNING = 2, + MEDIA_PAUSED = 3, + MEDIA_STOPPED = 4 + }; + enum ErrorCode { + MEDIA_ERR_NONE_ACTIVE = 0, + MEDIA_ERR_ABORTED = 1, + MEDIA_ERR_NETWORK = 2, + MEDIA_ERR_DECODE = 3, + MEDIA_ERR_NONE_SUPPORTED = 4 + }; + + void execJS(const QString &js) { + m_cordova->execJS(js); + } +public slots: + void create(int scId, int ecId, const QString &id, const QString &src); + void relase(int scId, int ecId, const QString &id); + + void startRecordingAudio(int scId, int ecId, const QString &id, const QString &src); + void stopRecordingAudio(int scId, int ecId, const QString &id); + + void startPlayingAudio(int scId, int ecId, const QString &id, const QString &src, QVariantMap options); + void pausePlayingAudio(int scId, int ecId, const QString &id); + void stopPlayingAudio(int scId, int ecId, const QString &id); + void getCurrentPositionAudio(int scId, int ecId, const QString &id); + void seekToAudio(int scId, int ecId, const QString &id, qint64 position); + void setVolume(int scId, int ecId, const QString &id, int volume); + +private: + QMap<QString, QSharedPointer<Player> > _id2Player; +}; + +class Player: public QObject { + Q_OBJECT +public: + Player(const QString &id, QString src, Media *plugin): + _state(Media::MEDIA_NONE), + _src(src), + _mode(MODE_NONE), + _plugin(plugin), + _id(id), + _stateChanged(false) { + QUrl url(src, QUrl::TolerantMode); + + if (url.scheme().isEmpty()) { + QAudioEncoderSettings audioSettings; + + _recorder.setEncodingSettings(audioSettings); + _recorder.setOutputLocation(QFileInfo(src).absoluteFilePath()); + + _player.setMedia(QUrl::fromLocalFile(QFileInfo(src).absoluteFilePath())); + } else { + _player.setMedia(url); + } + QObject::connect(&_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(onMediaStatusChanged(QMediaPlayer::MediaStatus))); + QObject::connect(&_recorder, SIGNAL(error(QMediaRecorder::Error)), this, SLOT(onError(QMediaRecorder::Error))); + + connect(&_timer, SIGNAL(timeout()), this, SLOT(reportPosition())); + } + + void startRecording() { + if (recordMode() && _state != Media::MEDIA_RUNNING) { + _recorder.record(); + setState(Media::MEDIA_RUNNING); + } + } + void stopRecording() { + if (recordMode() && _state == Media::MEDIA_RUNNING) { + _recorder.stop(); + setState(Media::MEDIA_STOPPED); + } + } + + void setVolume(int volume) { + _player.setVolume(volume); + } + + void play() { + if (playMode() && _state != Media::MEDIA_RUNNING) { + _player.play(); + setState(Media::MEDIA_RUNNING); + } + } + void pause() { + if (playMode() && _state == Media::MEDIA_RUNNING) { + _player.pause(); + setState(Media::MEDIA_PAUSED); + } + } + void stop() { + if (playMode() && (_state == Media::MEDIA_RUNNING || _state == Media::MEDIA_PAUSED)) { + _player.stop(); + setState(Media::MEDIA_STOPPED); + } + } + double getDuration() { + if (_mode == MODE_NONE || _player.duration() == -1) + return -1; + if (_mode != MODE_PLAY) + return -2; + return static_cast<double>(_player.duration()) / 1000.0; + } + double getPosition() { + if (_mode != MODE_PLAY) + return -1; + return static_cast<double>(_player.position()) / 1000.0; + } + bool seekTo(qint64 position) { + if (!_player.isSeekable()) + return false; + _player.setPosition(position * 1000); + return true; + } +private slots: + void reportPosition() { + double position = getPosition(); + _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_POSITION, %2)") + .arg(_id).arg(position)); + double duration = getDuration(); + _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_DURATION, %2)") + .arg(_id).arg(duration)); + + if (_stateChanged && !(_state == Media::MEDIA_RUNNING && (duration == -1 || position == 0))) { + qCritical() << _id << "POSITION" << position << ":" << duration; + _stateChanged = false; + _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_STATE, %2)").arg(_id).arg(_state)); + } + } + + void onMediaStatusChanged(QMediaPlayer::MediaStatus status) { + if (status == QMediaPlayer::InvalidMedia) { + reportError(Media::MEDIA_ERR_ABORTED, "AudioPlayer Error: The current media cannot be played."); + setState(Media::MEDIA_STOPPED); + } + if (status == QMediaPlayer::EndOfMedia) { + setState(Media::MEDIA_STOPPED); + seekTo(0); + } + } + void onError(QMediaRecorder::Error) { + reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: Device is not ready or not available."); + setState(Media::MEDIA_STOPPED); + } + +private: + void reportError(int code, const QString &descr) { + Q_UNUSED(descr); + _plugin->execJS(QString("Media.onStatus('%1', Media.MEDIA_ERROR, {code: %2})") + .arg(_id).arg(code)); + } + + bool playMode() { + switch (_mode) { + case Player::MODE_NONE: + _mode = MODE_PLAY; + break; + case Player::MODE_PLAY: + break; + case Player::MODE_RECORD: + reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: Can't play in record mode."); + return false; + break; + } + return true; + } + + bool recordMode() { + switch (_mode) { + case Player::MODE_NONE: + if (_recorder.outputLocation().isEmpty()) { + reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: unsupported output location."); + return false; + } + _mode = MODE_RECORD; + break; + case Player::MODE_PLAY: + reportError(Media::MEDIA_ERR_NONE_SUPPORTED, "AudioPlayer Error: Can't play in play mode."); + return false; + break; + case Player::MODE_RECORD: + break; + } + return true; + } + + void setState(Media::State state) { + _state = state; + _stateChanged = true; + _timer.start(250); + } + + QMediaPlayer _player; + + QAudioRecorder _recorder; + QTimer _timer; + + Media::State _state; + QString _src; + enum Mode { + MODE_NONE, + MODE_PLAY, + MODE_RECORD + }; + Mode _mode; + Media *_plugin; + QString _id; + + bool _stateChanged; +}; + +#endif diff --git a/plugins/org.apache.cordova.media/src/windows8/MediaProxy.js b/plugins/org.apache.cordova.media/src/windows8/MediaProxy.js new file mode 100644 index 00000000..e7ec51b7 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/windows8/MediaProxy.js @@ -0,0 +1,217 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/*global Windows:true */ + +var cordova = require('cordova'), + Media = require('org.apache.cordova.media.Media'); + +var MediaError = require('org.apache.cordova.media.MediaError'); + +var recordedFile; + +module.exports = { + mediaCaptureMrg:null, + + // Initiates the audio file + create:function(win, lose, args) { + var id = args[0]; + var src = args[1]; + var thisM = Media.get(id); + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STARTING); + + Media.prototype.node = null; + + var fn = src.split('.').pop(); // gets the file extension + if (thisM.node === null) { + if (fn === 'mp3' || fn === 'wma' || fn === 'wav' || + fn === 'cda' || fn === 'adx' || fn === 'wm' || + fn === 'm3u' || fn === 'wmx' || fn === 'm4a') { + thisM.node = new Audio(src); + thisM.node.load(); + + var getDuration = function () { + var dur = thisM.node.duration; + if (isNaN(dur)) { + dur = -1; + } + Media.onStatus(id, Media.MEDIA_DURATION, dur); + }; + + thisM.node.onloadedmetadata = getDuration; + getDuration(); + } + else { + lose && lose({code:MediaError.MEDIA_ERR_ABORTED}); + } + } + }, + + // Start playing the audio + startPlayingAudio:function(win, lose, args) { + var id = args[0]; + //var src = args[1]; + //var options = args[2]; + + var thisM = Media.get(id); + // if Media was released, then node will be null and we need to create it again + if (!thisM.node) { + module.exports.create(win, lose, args); + } + + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_RUNNING); + + thisM.node.play(); + }, + + // Stops the playing audio + stopPlayingAudio:function(win, lose, args) { + var id = args[0]; + try { + (Media.get(id)).node.pause(); + (Media.get(id)).node.currentTime = 0; + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_STOPPED); + win(); + } catch (err) { + lose("Failed to stop: "+err); + } + }, + + // Seeks to the position in the audio + seekToAudio:function(win, lose, args) { + var id = args[0]; + var milliseconds = args[1]; + try { + (Media.get(id)).node.currentTime = milliseconds / 1000; + win(); + } catch (err) { + lose("Failed to seek: "+err); + } + }, + + // Pauses the playing audio + pausePlayingAudio:function(win, lose, args) { + var id = args[0]; + var thisM = Media.get(id); + try { + thisM.node.pause(); + Media.onStatus(id, Media.MEDIA_STATE, Media.MEDIA_PAUSED); + } catch (err) { + lose("Failed to pause: "+err); + } + }, + + // Gets current position in the audio + getCurrentPositionAudio:function(win, lose, args) { + var id = args[0]; + try { + var p = (Media.get(id)).node.currentTime; + Media.onStatus(id, Media.MEDIA_POSITION, p); + win(p); + } catch (err) { + lose(err); + } + }, + + // Start recording audio + startRecordingAudio:function(win, lose, args) { + var id = args[0]; + var src = args[1]; + + var normalizedSrc = src.replace(/\//g, '\\'); + var destPath = normalizedSrc.substr(0, normalizedSrc.lastIndexOf('\\')); + var destFileName = normalizedSrc.replace(destPath + '\\', ''); + + // Initialize device + Media.prototype.mediaCaptureMgr = null; + var thisM = (Media.get(id)); + var captureInitSettings = new Windows.Media.Capture.MediaCaptureInitializationSettings(); + captureInitSettings.streamingCaptureMode = Windows.Media.Capture.StreamingCaptureMode.audio; + thisM.mediaCaptureMgr = new Windows.Media.Capture.MediaCapture(); + thisM.mediaCaptureMgr.addEventListener("failed", lose); + + thisM.mediaCaptureMgr.initializeAsync(captureInitSettings).done(function (result) { + thisM.mediaCaptureMgr.addEventListener("recordlimitationexceeded", lose); + thisM.mediaCaptureMgr.addEventListener("failed", lose); + + // Start recording + Windows.Storage.ApplicationData.current.temporaryFolder.createFileAsync(destFileName, Windows.Storage.CreationCollisionOption.replaceExisting).done(function (newFile) { + recordedFile = newFile; + var encodingProfile = null; + switch (newFile.fileType) { + case '.m4a': + encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createM4a(Windows.Media.MediaProperties.AudioEncodingQuality.auto); + break; + case '.mp3': + encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp3(Windows.Media.MediaProperties.AudioEncodingQuality.auto); + break; + case '.wma': + encodingProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createWma(Windows.Media.MediaProperties.AudioEncodingQuality.auto); + break; + default: + lose("Invalid file type for record"); + break; + } + thisM.mediaCaptureMgr.startRecordToStorageFileAsync(encodingProfile, newFile).done(win, lose); + }, lose); + }, lose); + }, + + // Stop recording audio + stopRecordingAudio:function(win, lose, args) { + var id = args[0]; + var thisM = Media.get(id); + + var normalizedSrc = thisM.src.replace(/\//g, '\\'); + var destPath = normalizedSrc.substr(0, normalizedSrc.lastIndexOf('\\')); + var destFileName = normalizedSrc.replace(destPath + '\\', ''); + + thisM.mediaCaptureMgr.stopRecordAsync().done(function () { + if (destPath) { + Windows.Storage.StorageFolder.getFolderFromPathAsync(destPath).done(function(destFolder) { + recordedFile.copyAsync(destFolder, destFileName, Windows.Storage.CreationCollisionOption.replaceExisting).done(win, lose); + }, lose); + } else { + // if path is not defined, we leave recorded file in temporary folder (similar to iOS) + win(); + } + }, lose); + }, + + // Release the media object + release:function(win, lose, args) { + var id = args[0]; + var thisM = Media.get(id); + try { + delete thisM.node; + } catch (err) { + lose("Failed to release: "+err); + } + }, + setVolume:function(win, lose, args) { + var id = args[0]; + var volume = args[1]; + var thisM = Media.get(id); + thisM.volume = volume; + } +}; + +require("cordova/exec/proxy").add("Media",module.exports); diff --git a/plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs b/plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs new file mode 100644 index 00000000..882eb96e --- /dev/null +++ b/plugins/org.apache.cordova.media/src/wp/AudioPlayer.cs @@ -0,0 +1,647 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.IO; +using System.IO.IsolatedStorage; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Threading; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Media; +using Microsoft.Phone.Controls; +using System.Diagnostics; +using System.Windows.Resources; + +namespace WPCordovaClassLib.Cordova.Commands +{ + + /// <summary> + /// Implements audio record and play back functionality. + /// </summary> + internal class AudioPlayer : IDisposable + { + #region Constants + + // AudioPlayer states + private const int PlayerState_None = 0; + private const int PlayerState_Starting = 1; + private const int PlayerState_Running = 2; + private const int PlayerState_Paused = 3; + private const int PlayerState_Stopped = 4; + + // AudioPlayer messages + private const int MediaState = 1; + private const int MediaDuration = 2; + private const int MediaPosition = 3; + private const int MediaError = 9; + + // AudioPlayer errors + private const int MediaErrorPlayModeSet = 1; + private const int MediaErrorAlreadyRecording = 2; + private const int MediaErrorStartingRecording = 3; + private const int MediaErrorRecordModeSet = 4; + private const int MediaErrorStartingPlayback = 5; + private const int MediaErrorResumeState = 6; + private const int MediaErrorPauseState = 7; + private const int MediaErrorStopState = 8; + + //TODO: get rid of this callback, it should be universal + //private const string CallbackFunction = "CordovaMediaonStatus"; + + #endregion + + + /// <summary> + /// The AudioHandler object + /// </summary> + private Media handler; + + /// <summary> + /// Temporary buffer to store audio chunk + /// </summary> + private byte[] buffer; + + /// <summary> + /// Xna game loop dispatcher + /// </summary> + DispatcherTimer dtXna; + + + /// <summary> + /// Output buffer + /// </summary> + private MemoryStream memoryStream; + + /// <summary> + /// The id of this player (used to identify Media object in JavaScript) + /// </summary> + private String id; + + /// <summary> + /// State of recording or playback + /// </summary> + private int state = PlayerState_None; + + /// <summary> + /// File name to play or record to + /// </summary> + private String audioFile = null; + + /// <summary> + /// Duration of audio + /// </summary> + private double duration = -1; + + /// <summary> + /// Audio player object + /// </summary> + private MediaElement player = null; + + /// <summary> + /// Audio source + /// </summary> + private Microphone recorder; + + /// <summary> + /// Internal flag specified that we should only open audio w/o playing it + /// </summary> + private bool prepareOnly = false; + + /// <summary> + /// Creates AudioPlayer instance + /// </summary> + /// <param name="handler">Media object</param> + /// <param name="id">player id</param> + public AudioPlayer(Media handler, String id) + { + this.handler = handler; + this.id = id; + } + + + /// <summary> + /// Destroys player and stop audio playing or recording + /// </summary> + public void Dispose() + { + if (this.player != null) + { + this.stopPlaying(); + this.player = null; + } + if (this.recorder != null) + { + this.stopRecording(); + this.recorder = null; + } + + this.FinalizeXnaGameLoop(); + } + + private void InvokeCallback(int message, string value, bool removeHandler) + { + string args = string.Format("('{0}',{1},{2});", this.id, message, value); + string callback = @"(function(id,msg,value){ + try { + if (msg == Media.MEDIA_ERROR) { + value = {'code':value}; + } + Media.onStatus(id,msg,value); + } + catch(e) { + console.log('Error calling Media.onStatus :: ' + e); + } + })" + args; + this.handler.InvokeCustomScript(new ScriptCallback("eval", new string[] { callback }), false); + } + + private void InvokeCallback(int message, int value, bool removeHandler) + { + InvokeCallback(message, value.ToString(), removeHandler); + } + + private void InvokeCallback(int message, double value, bool removeHandler) + { + InvokeCallback(message, value.ToString(), removeHandler); + } + + /// <summary> + /// Starts recording, data is stored in memory + /// </summary> + /// <param name="filePath"></param> + public void startRecording(string filePath) + { + if (this.player != null) + { + InvokeCallback(MediaError, MediaErrorPlayModeSet, false); + } + else if (this.recorder == null) + { + try + { + this.audioFile = filePath; + this.InitializeXnaGameLoop(); + this.recorder = Microphone.Default; + this.recorder.BufferDuration = TimeSpan.FromMilliseconds(500); + this.buffer = new byte[recorder.GetSampleSizeInBytes(this.recorder.BufferDuration)]; + this.recorder.BufferReady += new EventHandler<EventArgs>(recorderBufferReady); + MemoryStream stream = new MemoryStream(); + this.memoryStream = stream; + int numBits = 16; + int numBytes = numBits / 8; + + // inline version from AudioFormatsHelper + stream.Write(System.Text.Encoding.UTF8.GetBytes("RIFF"), 0, 4); + stream.Write(BitConverter.GetBytes(0), 0, 4); + stream.Write(System.Text.Encoding.UTF8.GetBytes("WAVE"), 0, 4); + stream.Write(System.Text.Encoding.UTF8.GetBytes("fmt "), 0, 4); + stream.Write(BitConverter.GetBytes(16), 0, 4); + stream.Write(BitConverter.GetBytes((short)1), 0, 2); + stream.Write(BitConverter.GetBytes((short)1), 0, 2); + stream.Write(BitConverter.GetBytes(this.recorder.SampleRate), 0, 4); + stream.Write(BitConverter.GetBytes(this.recorder.SampleRate * numBytes), 0, 4); + stream.Write(BitConverter.GetBytes((short)(numBytes)), 0, 2); + stream.Write(BitConverter.GetBytes((short)(numBits)), 0, 2); + stream.Write(System.Text.Encoding.UTF8.GetBytes("data"), 0, 4); + stream.Write(BitConverter.GetBytes(0), 0, 4); + + this.recorder.Start(); + FrameworkDispatcher.Update(); + this.SetState(PlayerState_Running); + } + catch (Exception) + { + InvokeCallback(MediaError, MediaErrorStartingRecording, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStartingRecording),false); + } + } + else + { + InvokeCallback(MediaError, MediaErrorAlreadyRecording, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorAlreadyRecording),false); + } + } + + /// <summary> + /// Stops recording + /// </summary> + public void stopRecording() + { + if (this.recorder != null) + { + if (this.state == PlayerState_Running) + { + try + { + this.recorder.Stop(); + this.recorder.BufferReady -= recorderBufferReady; + this.recorder = null; + SaveAudioClipToLocalStorage(); + this.FinalizeXnaGameLoop(); + this.SetState(PlayerState_Stopped); + } + catch (Exception) + { + //TODO + } + } + } + } + + /// <summary> + /// Starts or resume playing audio file + /// </summary> + /// <param name="filePath">The name of the audio file</param> + /// <summary> + /// Starts or resume playing audio file + /// </summary> + /// <param name="filePath">The name of the audio file</param> + public void startPlaying(string filePath) + { + if (this.recorder != null) + { + InvokeCallback(MediaError, MediaErrorRecordModeSet, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorRecordModeSet),false); + return; + } + + + if (this.player == null || this.player.Source.AbsolutePath.LastIndexOf(filePath) < 0) + { + try + { + // this.player is a MediaElement, it must be added to the visual tree in order to play + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + if (page != null) + { + Grid grid = page.FindName("LayoutRoot") as Grid; + if (grid != null) + { + + this.player = grid.FindName("playerMediaElement") as MediaElement; + if (this.player == null) // still null ? + { + this.player = new MediaElement(); + this.player.Name = "playerMediaElement"; + grid.Children.Add(this.player); + this.player.Visibility = Visibility.Visible; + } + if (this.player.CurrentState == System.Windows.Media.MediaElementState.Playing) + { + this.player.Stop(); // stop it! + } + + this.player.Source = null; // Garbage collect it. + this.player.MediaOpened += MediaOpened; + this.player.MediaEnded += MediaEnded; + this.player.MediaFailed += MediaFailed; + } + } + } + + this.audioFile = filePath; + + Uri uri = new Uri(filePath, UriKind.RelativeOrAbsolute); + if (uri.IsAbsoluteUri) + { + this.player.Source = uri; + } + else + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + if (!isoFile.FileExists(filePath)) + { + // try to unpack it from the dll into isolated storage + StreamResourceInfo fileResourceStreamInfo = Application.GetResourceStream(new Uri(filePath, UriKind.Relative)); + if (fileResourceStreamInfo != null) + { + using (BinaryReader br = new BinaryReader(fileResourceStreamInfo.Stream)) + { + byte[] data = br.ReadBytes((int)fileResourceStreamInfo.Stream.Length); + + string[] dirParts = filePath.Split('/'); + string dirName = ""; + for (int n = 0; n < dirParts.Length - 1; n++) + { + dirName += dirParts[n] + "/"; + } + if (!isoFile.DirectoryExists(dirName)) + { + isoFile.CreateDirectory(dirName); + } + + using (IsolatedStorageFileStream outFile = isoFile.OpenFile(filePath, FileMode.Create)) + { + using (BinaryWriter writer = new BinaryWriter(outFile)) + { + writer.Write(data); + } + } + } + } + } + if (isoFile.FileExists(filePath)) + { + using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, isoFile)) + { + this.player.SetSource(stream); + } + } + else + { + InvokeCallback(MediaError, MediaErrorPlayModeSet, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, 1), false); + return; + } + } + } + this.SetState(PlayerState_Starting); + } + catch (Exception e) + { + Debug.WriteLine("Error in AudioPlayer::startPlaying : " + e.Message); + InvokeCallback(MediaError, MediaErrorStartingPlayback, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStartingPlayback),false); + } + } + else + { + if (this.state != PlayerState_Running) + { + this.player.Play(); + this.SetState(PlayerState_Running); + } + else + { + InvokeCallback(MediaError, MediaErrorResumeState, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorResumeState),false); + } + } + } + + /// <summary> + /// Callback to be invoked when the media source is ready for playback + /// </summary> + private void MediaOpened(object sender, RoutedEventArgs arg) + { + if (this.player != null) + { + this.duration = this.player.NaturalDuration.TimeSpan.TotalSeconds; + InvokeCallback(MediaDuration, this.duration, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaDuration, this.duration),false); + if (!this.prepareOnly) + { + this.player.Play(); + this.SetState(PlayerState_Running); + } + this.prepareOnly = false; + } + else + { + // TODO: occasionally MediaOpened is signalled, but player is null + } + } + + /// <summary> + /// Callback to be invoked when playback of a media source has completed + /// </summary> + private void MediaEnded(object sender, RoutedEventArgs arg) + { + this.SetState(PlayerState_Stopped); + } + + /// <summary> + /// Callback to be invoked when playback of a media source has failed + /// </summary> + private void MediaFailed(object sender, RoutedEventArgs arg) + { + player.Stop(); + InvokeCallback(MediaError, MediaErrorStartingPlayback, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError.ToString(), "Media failed"),false); + } + + /// <summary> + /// Seek or jump to a new time in the track + /// </summary> + /// <param name="milliseconds">The new track position</param> + public void seekToPlaying(int milliseconds) + { + if (this.player != null) + { + TimeSpan tsPos = new TimeSpan(0, 0, 0, 0, milliseconds); + this.player.Position = tsPos; + InvokeCallback(MediaPosition, milliseconds / 1000.0f, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaPosition, milliseconds / 1000.0f),false); + } + } + + /// <summary> + /// Set the volume of the player + /// </summary> + /// <param name="vol">volume 0.0-1.0, default value is 0.5</param> + public void setVolume(double vol) + { + if (this.player != null) + { + this.player.Volume = vol; + } + } + + /// <summary> + /// Pauses playing + /// </summary> + public void pausePlaying() + { + if (this.state == PlayerState_Running) + { + this.player.Pause(); + this.SetState(PlayerState_Paused); + } + else + { + InvokeCallback(MediaError, MediaErrorPauseState, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorPauseState),false); + } + } + + + /// <summary> + /// Stops playing the audio file + /// </summary> + public void stopPlaying() + { + if ((this.state == PlayerState_Running) || (this.state == PlayerState_Paused)) + { + this.player.Stop(); + + this.player.Position = new TimeSpan(0L); + this.SetState(PlayerState_Stopped); + } + //else // Why is it an error to call stop on a stopped media? + //{ + // this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaError, MediaErrorStopState), false); + //} + } + + /// <summary> + /// Gets current position of playback + /// </summary> + /// <returns>current position</returns> + public double getCurrentPosition() + { + if ((this.state == PlayerState_Running) || (this.state == PlayerState_Paused)) + { + double currentPosition = this.player.Position.TotalSeconds; + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaPosition, currentPosition),false); + return currentPosition; + } + else + { + return 0; + } + } + + /// <summary> + /// Gets the duration of the audio file + /// </summary> + /// <param name="filePath">The name of the audio file</param> + /// <returns>track duration</returns> + public double getDuration(string filePath) + { + if (this.recorder != null) + { + return (-2); + } + + if (this.player != null) + { + return this.duration; + + } + else + { + this.prepareOnly = true; + this.startPlaying(filePath); + return this.duration; + } + } + + /// <summary> + /// Sets the state and send it to JavaScript + /// </summary> + /// <param name="state">state</param> + private void SetState(int state) + { + if (this.state != state) + { + InvokeCallback(MediaState, state, false); + //this.handler.InvokeCustomScript(new ScriptCallback(CallbackFunction, this.id, MediaState, state),false); + } + + this.state = state; + } + + #region record methods + + /// <summary> + /// Copies data from recorder to memory storages and updates recording state + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + private void recorderBufferReady(object sender, EventArgs e) + { + this.recorder.GetData(this.buffer); + this.memoryStream.Write(this.buffer, 0, this.buffer.Length); + } + + /// <summary> + /// Writes audio data from memory to isolated storage + /// </summary> + /// <returns></returns> + private void SaveAudioClipToLocalStorage() + { + if (memoryStream == null || memoryStream.Length <= 0) + { + return; + } + + long position = memoryStream.Position; + memoryStream.Seek(4, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes((int)memoryStream.Length - 8), 0, 4); + memoryStream.Seek(40, SeekOrigin.Begin); + memoryStream.Write(BitConverter.GetBytes((int)memoryStream.Length - 44), 0, 4); + memoryStream.Seek(position, SeekOrigin.Begin); + + try + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + string directory = Path.GetDirectoryName(audioFile); + + if (!isoFile.DirectoryExists(directory)) + { + isoFile.CreateDirectory(directory); + } + + this.memoryStream.Seek(0, SeekOrigin.Begin); + + using (IsolatedStorageFileStream fileStream = isoFile.CreateFile(audioFile)) + { + this.memoryStream.CopyTo(fileStream); + } + } + } + catch (Exception) + { + //TODO: log or do something else + throw; + } + } + + #region Xna loop + /// <summary> + /// Special initialization required for the microphone: XNA game loop + /// </summary> + private void InitializeXnaGameLoop() + { + // Timer to simulate the XNA game loop (Microphone is from XNA) + this.dtXna = new DispatcherTimer(); + this.dtXna.Interval = TimeSpan.FromMilliseconds(33); + this.dtXna.Tick += delegate { try { FrameworkDispatcher.Update(); } catch { } }; + this.dtXna.Start(); + } + /// <summary> + /// Finalizes XNA game loop for microphone + /// </summary> + private void FinalizeXnaGameLoop() + { + // Timer to simulate the XNA game loop (Microphone is from XNA) + if (this.dtXna != null) + { + this.dtXna.Stop(); + this.dtXna = null; + } + } + + #endregion + + #endregion + } +} diff --git a/plugins/org.apache.cordova.media/src/wp/Media.cs b/plugins/org.apache.cordova.media/src/wp/Media.cs new file mode 100644 index 00000000..aedd2bb6 --- /dev/null +++ b/plugins/org.apache.cordova.media/src/wp/Media.cs @@ -0,0 +1,590 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Windows; +using System.Diagnostics; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// <summary> + /// Provides the ability to record and play back audio files on a device. + /// </summary> + public class Media : BaseCommand + { + /// <summary> + /// Audio player objects + /// </summary> + private static Dictionary<string, AudioPlayer> players = new Dictionary<string, AudioPlayer>(); + + /// <summary> + /// Represents Media action options. + /// </summary> + [DataContract] + public class MediaOptions + { + /// <summary> + /// Audio id + /// </summary> + [DataMember(Name = "id", IsRequired = true)] + public string Id { get; set; } + + /// <summary> + /// Path to audio file + /// </summary> + [DataMember(Name = "src")] + public string Src { get; set; } + + /// <summary> + /// New track position + /// </summary> + [DataMember(Name = "milliseconds")] + public int Milliseconds { get; set; } + + public string CallbackId { get; set; } + } + + /// <summary> + /// Releases the audio player instance to save memory. + /// </summary> + public void release(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + MediaOptions mediaOptions = new MediaOptions(); + + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + mediaOptions.Id = optionsString[0]; + callbackId = mediaOptions.CallbackId = optionsString[1]; + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + + if (!Media.players.ContainsKey(mediaOptions.Id)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, false), callbackId); + return; + } + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + AudioPlayer audio = Media.players[mediaOptions.Id]; + Media.players.Remove(mediaOptions.Id); + audio.Dispose(); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true), mediaOptions.CallbackId); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), mediaOptions.CallbackId); + } + }); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + } + + private AudioPlayer GetOrCreatePlayerById(string id) + { + AudioPlayer audio = null; + + lock (Media.players) + { + if (!Media.players.TryGetValue(id, out audio)) + { + audio = new AudioPlayer(this, id); + Media.players.Add(id, audio); + Debug.WriteLine("Media Created in GetOrCreatePlayerById"); + } + } + + + + return audio; + } + + /// <summary> + /// Starts recording and save the specified file + /// </summary> + public void startRecordingAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + MediaOptions mediaOptions = new MediaOptions(); + + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + mediaOptions.Id = optionsString[0]; + mediaOptions.Src = optionsString[1]; + callbackId = mediaOptions.CallbackId = optionsString[2]; + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), mediaOptions.CallbackId); + return; + } + + if (mediaOptions != null) + { + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + AudioPlayer audio; + if (!Media.players.ContainsKey(mediaOptions.Id)) + { + audio = new AudioPlayer(this, mediaOptions.Id); + Media.players.Add(mediaOptions.Id, audio); + } + else + { + audio = Media.players[mediaOptions.Id]; + } + + if (audio != null) + { + audio.startRecording(mediaOptions.Src); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), mediaOptions.CallbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, + "Error accessing AudioPlayer for key " + mediaOptions.Id), mediaOptions.CallbackId); + } + + + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), mediaOptions.CallbackId); + } + + }); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), mediaOptions.CallbackId); + } + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + } + + /// <summary> + /// Stops recording and save to the file specified when recording started + /// </summary> + public void stopRecordingAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + + try + { + string[] optStrings = JSON.JsonHelper.Deserialize<string[]>(options); + string mediaId = optStrings[0]; + callbackId = optStrings[1]; + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + if (Media.players.ContainsKey(mediaId)) + { + AudioPlayer audio = Media.players[mediaId]; + audio.stopRecording(); + Media.players.Remove(mediaId); + } + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + }); + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + } + } + + public void setVolume(string options) // id,volume + { + string callbackId = this.CurrentCommandCallbackId; + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + string id = optionsString[0]; + double volume = 0.0d; + double.TryParse(optionsString[1], out volume); + + callbackId = optionsString[2]; + + if (Media.players.ContainsKey(id)) + { + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + AudioPlayer player = Media.players[id]; + player.setVolume(volume); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + }); + } + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, + "Error parsing options into setVolume method"), callbackId); + } + } + + // Some Audio Notes: + // In the Windows Phone Emulator, playback of video or audio content using the MediaElement control is not supported. + // While playing, a MediaElement stops all other media playback on the phone. + // Multiple MediaElement controls are NOT supported + + // Called when you create a new Media('blah.wav') object in JS. + public void create(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + MediaOptions mediaOptions; + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + mediaOptions = new MediaOptions(); + mediaOptions.Id = optionsString[0]; + mediaOptions.Src = optionsString[1]; + callbackId = mediaOptions.CallbackId = optionsString[2]; + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, + "Error parsing options into create method"), callbackId); + return; + } + + GetOrCreatePlayerById(mediaOptions.Id); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + } + + /// <summary> + /// Starts or resume playing audio file + /// </summary> + public void startPlayingAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + MediaOptions mediaOptions; + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + mediaOptions = new MediaOptions(); + mediaOptions.Id = optionsString[0]; + mediaOptions.Src = optionsString[1]; + int msec = 0; + if (int.TryParse(optionsString[2], out msec)) + { + mediaOptions.Milliseconds = msec; + } + callbackId = mediaOptions.CallbackId = optionsString[3]; + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + + AudioPlayer audio = GetOrCreatePlayerById(mediaOptions.Id); + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + audio.startPlaying(mediaOptions.Src); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + }); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + } + + + /// <summary> + /// Seeks to a location + /// </summary> + public void seekToAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + MediaOptions mediaOptions; + + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + mediaOptions = new MediaOptions(); + mediaOptions.Id = optionsString[0]; + int msec = 0; + if (int.TryParse(optionsString[2], out msec)) + { + mediaOptions.Milliseconds = msec; + } + callbackId = mediaOptions.CallbackId = optionsString[3]; + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + if (Media.players.ContainsKey(mediaOptions.Id)) + { + AudioPlayer audio = Media.players[mediaOptions.Id]; + audio.seekToPlaying(mediaOptions.Milliseconds); + } + else + { + Debug.WriteLine("ERROR: seekToAudio could not find mediaPlayer for " + mediaOptions.Id); + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + }); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + } + + /// <summary> + /// Pauses playing + /// </summary> + public void pausePlayingAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + string mediaId = optionsString[0]; + callbackId = optionsString[1]; + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + if (Media.players.ContainsKey(mediaId)) + { + AudioPlayer audio = Media.players[mediaId]; + audio.pausePlaying(); + } + else + { + Debug.WriteLine("ERROR: pausePlayingAudio could not find mediaPlayer for " + mediaId); + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message),callbackId); + } + }); + + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); + } + + + } + + + /// <summary> + /// Stops playing the audio file + /// </summary> + public void stopPlayingAudio(String options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + string[] optionsStrings = JSON.JsonHelper.Deserialize<string[]>(options); + string mediaId = optionsStrings[0]; + callbackId = optionsStrings[1]; + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + if (Media.players.ContainsKey(mediaId)) + { + AudioPlayer audio = Media.players[mediaId]; + audio.stopPlaying(); + } + else + { + Debug.WriteLine("stopPlaying could not find mediaPlayer for " + mediaId); + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + }); + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + } + } + + /// <summary> + /// Gets current position of playback + /// </summary> + public void getCurrentPositionAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + string[] optionsStrings = JSON.JsonHelper.Deserialize<string[]>(options); + string mediaId = optionsStrings[0]; + callbackId = optionsStrings[1]; + Deployment.Current.Dispatcher.BeginInvoke(() => + { + try + { + if (Media.players.ContainsKey(mediaId)) + { + AudioPlayer audio = Media.players[mediaId]; + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, audio.getCurrentPosition()), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, -1), callbackId); + } + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + }); + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + } + + + /// <summary> + /// Gets the duration of the audio file + /// </summary> + + [Obsolete("This method will be removed shortly")] + public void getDurationAudio(string options) + { + string callbackId = this.CurrentCommandCallbackId; + try + { + MediaOptions mediaOptions; + + try + { + string[] optionsString = JSON.JsonHelper.Deserialize<string[]>(options); + + mediaOptions = new MediaOptions(); + mediaOptions.Id = optionsString[0]; + mediaOptions.Src = optionsString[1]; + callbackId = mediaOptions.CallbackId = optionsString[2]; + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); + return; + } + + AudioPlayer audio; + if (Media.players.ContainsKey(mediaOptions.Id)) + { + audio = Media.players[mediaOptions.Id]; + } + else + { + Debug.WriteLine("ERROR: getDurationAudio could not find mediaPlayer for " + mediaOptions.Id); + audio = new AudioPlayer(this, mediaOptions.Id); + Media.players.Add(mediaOptions.Id, audio); + } + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, audio.getDuration(mediaOptions.Src)), callbackId); + }); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message), callbackId); + } + } + } +} diff --git a/plugins/org.apache.cordova.media/tests/plugin.xml b/plugins/org.apache.cordova.media/tests/plugin.xml new file mode 100644 index 00000000..5c20c50d --- /dev/null +++ b/plugins/org.apache.cordova.media/tests/plugin.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:rim="http://www.blackberry.com/ns/widgets" + xmlns:android="http://schemas.android.com/apk/res/android" + id="org.apache.cordova.media.tests" + version="0.2.16"> + <name>Cordova Media Plugin Tests</name> + <license>Apache 2.0</license> + <js-module src="tests.js" name="tests"> + </js-module> +</plugin> diff --git a/plugins/org.apache.cordova.media/tests/tests.js b/plugins/org.apache.cordova.media/tests/tests.js new file mode 100644 index 00000000..3e8ad9f3 --- /dev/null +++ b/plugins/org.apache.cordova.media/tests/tests.js @@ -0,0 +1,836 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +exports.defineAutoTests = function () { + var failed = function (done, msg, error) { + var info = typeof msg == 'undefined' ? 'Unexpected error callback' : msg; + expect(true).toFailWithMessage(info + '\n' + JSON.stringify(error)); + done(); + }; + + var succeed = function (done, msg) { + var info = typeof msg == 'undefined' ? 'Unexpected success callback' : msg; + expect(true).toFailWithMessage(info); + done(); + }; + + describe('Media', function () { + + beforeEach(function () { + // Custom Matcher + jasmine.Expectation.addMatchers({ + toFailWithMessage : function () { + return { + compare : function (error, message) { + var pass = false; + return { + pass : pass, + message : message + }; + } + }; + } + }); + }); + + it("media.spec.1 should exist", function () { + expect(Media).toBeDefined(); + expect(typeof Media).toBe("function"); + }); + + it("media.spec.2 should have the following properties", function () { + var media1 = new Media("dummy"); + expect(media1.id).toBeDefined(); + expect(media1.src).toBeDefined(); + expect(media1._duration).toBeDefined(); + expect(media1._position).toBeDefined(); + media1.release(); + }); + + it("media.spec.3 should define constants for Media status", function () { + expect(Media).toBeDefined(); + expect(Media.MEDIA_NONE).toBe(0); + expect(Media.MEDIA_STARTING).toBe(1); + expect(Media.MEDIA_RUNNING).toBe(2); + expect(Media.MEDIA_PAUSED).toBe(3); + expect(Media.MEDIA_STOPPED).toBe(4); + }); + + it("media.spec.4 should define constants for Media errors", function () { + expect(MediaError).toBeDefined(); + expect(MediaError.MEDIA_ERR_NONE_ACTIVE).toBe(0); + expect(MediaError.MEDIA_ERR_ABORTED).toBe(1); + expect(MediaError.MEDIA_ERR_NETWORK).toBe(2); + expect(MediaError.MEDIA_ERR_DECODE).toBe(3); + expect(MediaError.MEDIA_ERR_NONE_SUPPORTED).toBe(4); + }); + + it("media.spec.5 should contain a play function", function () { + var media1 = new Media(); + expect(media1.play).toBeDefined(); + expect(typeof media1.play).toBe('function'); + media1.release(); + }); + + it("media.spec.6 should contain a stop function", function () { + var media1 = new Media(); + expect(media1.stop).toBeDefined(); + expect(typeof media1.stop).toBe('function'); + media1.release(); + }); + + it("media.spec.7 should contain a seekTo function", function () { + var media1 = new Media(); + expect(media1.seekTo).toBeDefined(); + expect(typeof media1.seekTo).toBe('function'); + media1.release(); + }); + + it("media.spec.8 should contain a pause function", function () { + var media1 = new Media(); + expect(media1.pause).toBeDefined(); + expect(typeof media1.pause).toBe('function'); + media1.release(); + }); + + it("media.spec.9 should contain a getDuration function", function () { + var media1 = new Media(); + expect(media1.getDuration).toBeDefined(); + expect(typeof media1.getDuration).toBe('function'); + media1.release(); + }); + + it("media.spec.10 should contain a getCurrentPosition function", function () { + var media1 = new Media(); + expect(media1.getCurrentPosition).toBeDefined(); + expect(typeof media1.getCurrentPosition).toBe('function'); + media1.release(); + }); + + it("media.spec.11 should contain a startRecord function", function () { + var media1 = new Media(); + expect(media1.startRecord).toBeDefined(); + expect(typeof media1.startRecord).toBe('function'); + media1.release(); + }); + + it("media.spec.12 should contain a stopRecord function", function () { + var media1 = new Media(); + expect(media1.stopRecord).toBeDefined(); + expect(typeof media1.stopRecord).toBe('function'); + media1.release(); + }); + + it("media.spec.13 should contain a release function", function () { + var media1 = new Media(); + expect(media1.release).toBeDefined(); + expect(typeof media1.release).toBe('function'); + media1.release(); + }); + + it("media.spec.14 should contain a setVolume function", function () { + var media1 = new Media(); + expect(media1.setVolume).toBeDefined(); + expect(typeof media1.setVolume).toBe('function'); + media1.release(); + }); + + it("media.spec.15 should return MediaError for bad filename", function (done) { + var fileName = 'invalid.file.name'; + //Error callback it has an unexpected behavior under Windows Phone, + //it enters to the error callback several times, instead of just one walk-through + //JIRA issue related with all details: CB-7092 + //the conditional statement should be removed once the issue is fixed. + + //bb10 dialog pops up, preventing tests from running + if (cordova.platformId === 'blackberry10' || cordova.platformId === 'windowsphone') { + expect(true).toFailWithMessage('Platform does not support this feature'); + done(); + } else { + var badMedia = null; + badMedia = new Media(fileName, succeed.bind(null, done, ' badMedia = new Media , Unexpected succees callback, it should not create Media object with invalid file name'), function (result) { + expect(result).toBeDefined(); + expect(result.code).toBe(MediaError.MEDIA_ERR_ABORTED); + if (badMedia !== null) { + badMedia.release(); + } + done(); + }); + badMedia.play(); + } + }); + + it("media.spec.16 position should be set properly", function (done) { + var mediaFile = 'http://cordova.apache.org/downloads/BlueZedEx.mp3', + mediaState = Media.MEDIA_STOPPED, + successCallback, + flag = true, + statusChange = function (statusCode) { + if (statusCode == Media.MEDIA_RUNNING && flag) { + //flag variable used to ensure an extra security statement to ensure that the callback is processed only once, + //in case for some reason the statusChange callback is reached more than one time with the same status code. + //Some information about this kind of behavior it can be found at JIRA: CB-7099 + flag = false; + setTimeout(function () { + media1.getCurrentPosition(function (position) { + expect(position).toBeGreaterThan(0.0); + media1.stop(); + media1.release(); + done(); + }, failed.bind(null, done, 'media1.getCurrentPosition - Error getting media current position')); + }, 1000); + } + }, + media1 = new Media(mediaFile, successCallback, failed.bind(null, done, 'media1 = new Media - Error creating Media object. Media file: ' + mediaFile), statusChange); + media1.play(); + }); + + it("media.spec.17 duration should be set properly", function (done) { + if (cordova.platformId === 'blackberry10') { + expect(true).toFailWithMessage('Platform does not supported this feature'); + done(); + } + var mediaFile = 'http://cordova.apache.org/downloads/BlueZedEx.mp3', + mediaState = Media.MEDIA_STOPPED, + successCallback, + flag = true, + statusChange = function (statusCode) { + if (statusCode == Media.MEDIA_RUNNING && flag) { + //flag variable used to ensure an extra security statement to ensure that the callback is processed only once, + //in case for some reason the statusChange callback is reached more than one time with the same status code. + //Some information about this kind of behavior it can be found at JIRA: CB-7099. + flag = false; + setTimeout(function () { + expect(media1.getDuration()).toBeGreaterThan(0.0); + media1.stop(); + media1.release(); + done(); + }, 1000); + } + }, + media1 = new Media(mediaFile, successCallback, failed.bind(null, done, 'media1 = new Media - Error creating Media object. Media file: ' + mediaFile), statusChange); + media1.play(); + }); + }); +}; + +//****************************************************************************************** +//***************************************Manual Tests*************************************** +//****************************************************************************************** + +exports.defineManualTests = function (contentEl, createActionButton) { + //------------------------------------------------------------------------- + // Audio player + //------------------------------------------------------------------------- + var media1 = null; + var media1Timer = null; + var audioSrc = null; + var defaultaudio = "http://cordova.apache.org/downloads/BlueZedEx.mp3"; + + //Play audio function + function playAudio(url) { + console.log("playAudio()"); + console.log(" -- media=" + media1); + + var src = defaultaudio; + + if (url) { + src = url; + } + + // Stop playing if src is different from currently playing source + if (src !== audioSrc) { + if (media1 !== null) { + stopAudio(); + media1 = null; + } + } + + if (media1 === null) { + + // TEST STREAMING AUDIO PLAYBACK + //var src = "http://nunzioweb.com/misc/Bon_Jovi-Crush_Mystery_Train.mp3"; // works + //var src = "http://nunzioweb.com/misc/Bon_Jovi-Crush_Mystery_Train.m3u"; // doesn't work + //var src = "http://www.wav-sounds.com/cartoon/bugsbunny1.wav"; // works + //var src = "http://www.angelfire.com/fl5/html-tutorial/a/couldyou.mid"; // doesn't work + //var src = "MusicSearch/mp3/train.mp3"; // works + //var src = "bryce.mp3"; // works + //var src = "/android_asset/www/bryce.mp3"; // works + + media1 = new Media(src, + function () { + console.log("playAudio():Audio Success"); + }, + function (err) { + console.log("playAudio():Audio Error: " + err.code); + setAudioStatus("Error: " + err.code); + }, + function (status) { + console.log("playAudio():Audio Status: " + status); + setAudioStatus(Media.MEDIA_MSG[status]); + + // If stopped, then stop getting current position + if (Media.MEDIA_STOPPED == status) { + clearInterval(media1Timer); + media1Timer = null; + setAudioPosition("0 sec"); + } + }); + } + audioSrc = src; + document.getElementById('durationValue').innerHTML = ""; + // Play audio + media1.play(); + if (media1Timer === null && media1.getCurrentPosition) { + media1Timer = setInterval( + function () { + media1.getCurrentPosition( + function (position) { + if (position >= 0.0) { + setAudioPosition(position + " sec"); + } + }, + function (e) { + console.log("Error getting pos=" + e); + setAudioPosition("Error: " + e); + }); + }, + 1000); + } + + // Get duration + var counter = 0; + var timerDur = setInterval( + function () { + counter = counter + 100; + if (counter > 2000) { + clearInterval(timerDur); + } + var dur = media1.getDuration(); + if (dur > 0) { + clearInterval(timerDur); + document.getElementById('durationValue').innerHTML = dur + " sec"; + } + }, 100); + } + + //Pause audio playback + function pauseAudio() { + console.log("pauseAudio()"); + if (media1) { + media1.pause(); + } + } + + //Stop audio + function stopAudio() { + console.log("stopAudio()"); + if (media1) { + media1.stop(); + } + clearInterval(media1Timer); + media1Timer = null; + } + + //Release audio + function releaseAudio() { + console.log("releaseAudio()"); + if (media1) { + media1.stop(); //imlied stop of playback, resets timer + media1.release(); + } + } + + //Set audio status + function setAudioStatus(status) { + document.getElementById('statusValue').innerHTML = status; + } + + //Set audio position + function setAudioPosition(position) { + document.getElementById('positionValue').innerHTML = position; + } + + //Seek audio + function seekAudio(mode) { + var time = document.getElementById("seekInput").value; + if (time === "") { + time = 5000; + } else { + time = time * 1000; //we expect the input to be in seconds + } + if (media1 === null) { + console.log("seekTo requested while media1 is null"); + if (audioSrc === null) { + audioSrc = defaultaudio; + } + media1 = new Media(audioSrc, + function () { + console.log("seekToAudio():Audio Success"); + }, + function (err) { + console.log("seekAudio():Audio Error: " + err.code); + setAudioStatus("Error: " + err.code); + }, + function (status) { + console.log("seekAudio():Audio Status: " + status); + setAudioStatus(Media.MEDIA_MSG[status]); + + // If stopped, then stop getting current position + if (Media.MEDIA_STOPPED == status) { + clearInterval(media1Timer); + media1Timer = null; + setAudioPosition("0 sec"); + } + }); + } + + media1.getCurrentPosition( + function (position) { + var deltat = time; + if (mode === "by") { + deltat = time + position * 1000; + } + media1.seekTo(deltat, + function () { + console.log("seekAudioTo():Audio Success"); + //force an update on the position display + updatePosition(); + }, + function (err) { + console.log("seekAudioTo():Audio Error: " + err.code); + }); + }, + function (e) { + console.log("Error getting pos=" + e); + setAudioPosition("Error: " + e); + }); + } + + //for forced updates of position after a successful seek + + function updatePosition() { + media1.getCurrentPosition( + function (position) { + if (position >= 0.0) { + setAudioPosition(position + " sec"); + } + }, + function (e) { + console.log("Error getting pos=" + e); + setAudioPosition("Error: " + e); + }); + } + + //------------------------------------------------------------------------- + // Audio recorder + //------------------------------------------------------------------------- + var mediaRec = null; + var recTime = 0; + var recordSrc = "myRecording.mp3"; + + //Record audio + function recordAudio() { + console.log("recordAudio()"); + console.log(" -- media=" + mediaRec); + if (mediaRec == null) { + var src = recordSrc; + mediaRec = new Media(src, + function () { + console.log("recordAudio():Audio Success"); + }, + function (err) { + console.log("recordAudio():Audio Error: " + err.code); + setAudioStatus("Error: " + err.code); + }, + function (status) { + console.log("recordAudio():Audio Status: " + status); + setAudioStatus(Media.MEDIA_MSG[status]); + }); + } + + // Record audio + mediaRec.startRecord(); + + // Stop recording after 10 sec + recTime = 0; + var recInterval = setInterval(function () { + recTime = recTime + 1; + setAudioPosition(recTime + " sec"); + if (recTime >= 10) { + clearInterval(recInterval); + if (mediaRec.stopAudioRecord) { + mediaRec.stopAudioRecord(); + } else { + mediaRec.stopRecord(); + } + console.log("recordAudio(): stop"); + } + }, 1000); + } + + //Play back recorded audio + function playRecording() { + playAudio(recordSrc); + } + + //Function to create a file for iOS recording + + function getRecordSrc() { + var fsFail = function (error) { + console.log("error creating file for iOS recording"); + }; + var gotFile = function (file) { + recordSrc = file.fullPath; + //console.log("recording Src: " + recordSrc); + }; + var gotFS = function (fileSystem) { + fileSystem.root.getFile("iOSRecording.wav", { + create : true + }, gotFile, fsFail); + }; + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, gotFS, fsFail); + } + + //Function to create a file for BB recording + function getRecordSrcBB() { + var fsFail = function (error) { + console.log("error creating file for BB recording"); + }; + var gotFile = function (file) { + recordSrc = file.fullPath; + }; + var gotFS = function (fileSystem) { + fileSystem.root.getFile("BBRecording.amr", { + create : true + }, gotFile, fsFail); + }; + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, gotFS, fsFail); + } + + //Function to create a file for Windows recording + function getRecordSrcWin() { + var fsFail = function (error) { + console.log("error creating file for Win recording"); + }; + var gotFile = function (file) { + recordSrc = file.fullPath.replace(/\//g, '\\'); + }; + var gotFS = function (fileSystem) { + fileSystem.root.getFile("WinRecording.m4a", { + create: true + }, gotFile, fsFail); + }; + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, gotFS, fsFail); + } + +//Generate Dynamic Table + function generateTable(tableId, rows, cells, elements) { + var table = document.createElement('table'); + for (var r = 0; r < rows; r++) { + var row = table.insertRow(r); + for (var c = 0; c < cells; c++) { + var cell = row.insertCell(c); + cell.setAttribute("align", "center"); + for (var e in elements) { + if (elements[e].position.row == r && elements[e].position.cell == c) { + var htmlElement = document.createElement(elements[e].tag); + var content; + + if (elements[e].content !== "") { + content = document.createTextNode(elements[e].content); + htmlElement.appendChild(content); + } + if (elements[e].type) { + htmlElement.type = elements[e].type; + } + htmlElement.setAttribute("id", elements[e].id); + cell.appendChild(htmlElement); + } + } + } + } + table.setAttribute("align", "center"); + table.setAttribute("id", tableId); + return table; + } + +//Audio && Record Elements + var elementsResultsAudio= + [{ + id : "statusTag", + content : "Status:", + tag : "div", + position : { + row : 0, + cell : 0 + } + }, { + id : "statusValue", + content : "", + tag : "div", + position : { + row : 0, + cell : 2 + } + }, { + id : "durationTag", + content : "Duration:", + tag : "div", + position : { + row : 1, + cell : 0 + } + }, { + id : "durationValue", + content : "", + tag : "div", + position : { + row : 1, + cell : 2 + } + }, { + id : "positionTag", + content : "Position:", + tag : "div", + position : { + row : 2, + cell : 0 + } + }, { + id : "positionValue", + content : "", + tag : "div", + position : { + row : 2, + cell : 2 + } + }], + elementsAudio = + [{ + id : "playBtn", + content : "", + tag : "div", + position : { + row : 0, + cell : 0 + } + }, { + id : "pauseBtn", + content : "", + tag : "div", + position : { + row : 0, + cell : 1 + } + }, { + id : "stopBtn", + content : "", + tag : "div", + position : { + row : 1, + cell : 0 + } + }, { + id : "releaseBtn", + content : "", + tag : "div", + position : { + row : 1, + cell : 1 + } + }, { + id : "seekByBtn", + content : "", + tag : "div", + position : { + row : 2, + cell : 0 + } + }, { + id : "seekToBtn", + content : "", + tag : "div", + position : { + row : 2, + cell : 1 + } + }, { + id : "seekInput", + content : "", + tag : "input", + type : "number", + position : { + row : 2, + cell : 2 + } + } + ], + elementsRecord = + [{ + id : "recordbtn", + content : "", + tag : "div", + position : { + row : 1, + cell : 0 + } + }, { + id : "recordplayBtn", + content : "", + tag : "div", + position : { + row : 1, + cell : 1 + } + }, { + id : "recordpauseBtn", + content : "", + tag : "div", + position : { + row : 2, + cell : 0 + } + }, { + id : "recordstopBtn", + content : "", + tag : "div", + position : { + row : 2, + cell : 1 + } + } + ]; + + //Title audio results + var div = document.createElement('h2'); + div.appendChild(document.createTextNode('Audio')); + div.setAttribute("align", "center"); + contentEl.appendChild(div); + //Generate and add results table + contentEl.appendChild(generateTable('info', 3, 3, elementsResultsAudio)); + + //Title audio actions + div = document.createElement('h2'); + div.appendChild(document.createTextNode('Actions')); + div.setAttribute("align", "center"); + contentEl.appendChild(div); + //Generate and add buttons table + contentEl.appendChild(generateTable('audioActions', 3, 3, elementsAudio)); + createActionButton('Play', function () { + playAudio(); + }, 'playBtn'); + createActionButton('Pause', function () { + pauseAudio(); + }, 'pauseBtn'); + createActionButton('Stop', function () { + stopAudio(); + }, 'stopBtn'); + createActionButton('Release', function () { + releaseAudio(); + }, 'releaseBtn'); + createActionButton('Seek By', function () { + seekAudio('by'); + }, 'seekByBtn'); + createActionButton('Seek To', function () { + seekAudio('to'); + }, 'seekToBtn'); + //get Special path to record if iOS || Blackberry + if (cordova.platformId === 'ios') + getRecordSrc(); + else if (cordova.platformId === 'blackberry') + getRecordSrcBB(); + else if (cordova.platformId === 'windows' || cordova.platformId === 'windows8') + getRecordSrcWin(); + + //testing process and details + function addItemToList(_list, _text) + { + var item = document.createElement('li'); + item.appendChild(document.createTextNode(_text)); + _list.appendChild(item); + } + + div = document.createElement('h4'); + div.appendChild(document.createTextNode('Recommended Test Procedure')); + contentEl.appendChild(div); + + var list = document.createElement('ol'); + addItemToList(list, 'Press play - Will start playing audio. Status: Running, Duration: 61.333 sec, Position: Current position of audio track'); + addItemToList(list, 'Press pause - Will pause the audio. Status: Paused, Duration: 61.333 sec, Position: Position where track was paused'); + addItemToList(list, 'Press play - Will begin playing where track left off from the pause'); + addItemToList(list, 'Press stop - Will stop the audio. Status: Stopped, Duration: 61.333 sec, Position: 0 sec'); + addItemToList(list, 'Press play - Will begin playing the audio track from the beginning'); + addItemToList(list, 'Press release - Will stop the audio. Status: Stopped, Duration: 61.333 sec, Position: 0 sec'); + addItemToList(list, 'Press play - Will begin playing the audio track from the beginning'); + addItemToList(list, 'Type 10 in the text box beside Seek To button'); + addItemToList(list, 'Press seek by - Will jump 10 seconds ahead in the audio track. Position: should jump by 10 sec'); + addItemToList(list, 'Press stop if track is not already stopped'); + addItemToList(list, 'Type 30 in the text box beside Seek To button'); + addItemToList(list, 'Press play then seek to - Should jump to Position 30 sec'); + addItemToList(list, 'Press stop'); + addItemToList(list, 'Type 5 in the text box beside Seek To button'); + addItemToList(list, 'Press play, let play past 10 seconds then press seek to - should jump back to position 5 sec'); + + div = document.createElement('div'); + div.setAttribute("style", "background:#B0C4DE;border:1px solid #FFA07A;margin:15px 6px 0px;min-width:295px;max-width:97%;padding:4px 0px 2px 10px;min-height:160px;max-height:200px;overflow:auto"); + div.appendChild(list); + contentEl.appendChild(div); + + //Title Record Audio + div = document.createElement('h2'); + div.appendChild(document.createTextNode('Record Audio')); + div.setAttribute("align", "center"); + contentEl.appendChild(div); + //Generate and add Record buttons table + contentEl.appendChild(generateTable('recordContent', 3, 3, elementsRecord)); + createActionButton('Record Audio \n 10 sec', function () { + recordAudio(); + }, 'recordbtn'); + createActionButton('Play', function () { + playRecording(); + }, 'recordplayBtn'); + createActionButton('Pause', function () { + pauseAudio(); + }, 'recordpauseBtn'); + createActionButton('Stop', function () { + stopAudio(); + }, 'recordstopBtn'); + + //testing process and details + div = document.createElement('h4'); + div.appendChild(document.createTextNode('Recommended Test Procedure')); + contentEl.appendChild(div); + + list = document.createElement('ol'); + addItemToList(list, 'Press Record Audio 10 sec - Will start recording audio for 10 seconds. Status: Running, Position: number of seconds recorded (will stop at 10)'); + addItemToList(list, 'Status will change to Stopped when finished recording'); + addItemToList(list, 'Press play - Will begin playing the recording. Status: Running, Duration: # of seconds of recording, Position: Current position of recording'); + addItemToList(list, 'Press stop - Will stop playing the recording. Status: Stopped, Duration: # of seconds of recording, Position: 0 sec'); + addItemToList(list, 'Press play - Will begin playing the recording from the beginning'); + addItemToList(list, 'Press pause - Will pause the playback of the recording. Status: Paused, Duration: # of seconds of recording, Position: Position where recording was paused'); + addItemToList(list, 'Press play - Will begin playing the recording from where it was paused'); + + div = document.createElement('div'); + div.setAttribute("style", "background:#B0C4DE;border:1px solid #FFA07A;margin:15px 6px 0px;min-width:295px;max-width:97%;padding:4px 0px 2px 10px;min-height:160px;max-height:200px;overflow:auto"); + div.appendChild(list); + contentEl.appendChild(div); +}; diff --git a/plugins/org.apache.cordova.media/www/Media.js b/plugins/org.apache.cordova.media/www/Media.js new file mode 100644 index 00000000..a2ae738c --- /dev/null +++ b/plugins/org.apache.cordova.media/www/Media.js @@ -0,0 +1,216 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'); + +var mediaObjects = {}; + +/** + * This class provides access to the device media, interfaces to both sound and video + * + * @constructor + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) - OPTIONAL + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) - OPTIONAL + */ +var Media = function(src, successCallback, errorCallback, statusCallback) { + argscheck.checkArgs('SFFF', 'Media', arguments); + this.id = utils.createUUID(); + mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this._duration = -1; + this._position = -1; + exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); +}; + +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_POSITION = 3; +Media.MEDIA_ERROR = 9; + +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; + +// "static" function to return existing objs. +Media.get = function(id) { + return mediaObjects[id]; +}; + +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function(options) { + exec(null, null, "Media", "startPlayingAudio", [this.id, this.src, options]); +}; + +/** + * Stop playing audio file. + */ +Media.prototype.stop = function() { + var me = this; + exec(function() { + me._position = 0; + }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); +}; + +/** + * Seek or jump to a new time in the track.. + */ +Media.prototype.seekTo = function(milliseconds) { + var me = this; + exec(function(p) { + me._position = p; + }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); +}; + +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + */ +Media.prototype.getCurrentPosition = function(success, fail) { + var me = this; + exec(function(p) { + me._position = p; + success(p); + }, fail, "Media", "getCurrentPositionAudio", [this.id]); +}; + +/** + * Start recording audio file. + */ +Media.prototype.startRecord = function() { + exec(null, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); +}; + +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + exec(null, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); +}; + +/** + * Release the resources. + */ +Media.prototype.release = function() { + exec(null, this.errorCallback, "Media", "release", [this.id]); +}; + +/** + * Adjust the volume. + */ +Media.prototype.setVolume = function(volume) { + exec(null, null, "Media", "setVolume", [this.id, volume]); +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param msgType The 'type' of update this is + * @param value Use of value is determined by the msgType + */ +Media.onStatus = function(id, msgType, value) { + + var media = mediaObjects[id]; + + if(media) { + switch(msgType) { + case Media.MEDIA_STATE : + media.statusCallback && media.statusCallback(value); + if(value == Media.MEDIA_STOPPED) { + media.successCallback && media.successCallback(); + } + break; + case Media.MEDIA_DURATION : + media._duration = value; + break; + case Media.MEDIA_ERROR : + media.errorCallback && media.errorCallback(value); + break; + case Media.MEDIA_POSITION : + media._position = Number(value); + break; + default : + console.error && console.error("Unhandled Media.onStatus :: " + msgType); + break; + } + } + else { + console.error && console.error("Received Media.onStatus callback for unknown media :: " + id); + } + +}; + +module.exports = Media; + +function onMessageFromNative(msg) { + if (msg.action == 'status') { + Media.onStatus(msg.status.id, msg.status.msgType, msg.status.value); + } else { + throw new Error('Unknown media action' + msg.action); + } +} + +if (cordova.platformId === 'android' || cordova.platformId === 'amazon-fireos') { + + var channel = require('cordova/channel'); + + channel.createSticky('onMediaPluginReady'); + channel.waitForInitialization('onMediaPluginReady'); + + channel.onCordovaReady.subscribe(function() { + exec(onMessageFromNative, undefined, 'Media', 'messageChannel', []); + channel.initializationComplete('onMediaPluginReady'); + }); +} diff --git a/plugins/org.apache.cordova.media/www/MediaError.js b/plugins/org.apache.cordova.media/www/MediaError.js new file mode 100644 index 00000000..38002ac5 --- /dev/null +++ b/plugins/org.apache.cordova.media/www/MediaError.js @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/** + * This class contains information about any Media errors. +*/ +/* + According to :: http://dev.w3.org/html5/spec-author-view/video.html#mediaerror + We should never be creating these objects, we should just implement the interface + which has 1 property for an instance, 'code' + + instead of doing : + errorCallbackFunction( new MediaError(3,'msg') ); +we should simply use a literal : + errorCallbackFunction( {'code':3} ); + */ + + var _MediaError = window.MediaError; + + +if(!_MediaError) { + window.MediaError = _MediaError = function(code, msg) { + this.code = (typeof code != 'undefined') ? code : null; + this.message = msg || ""; // message is NON-standard! do not use! + }; +} + +_MediaError.MEDIA_ERR_NONE_ACTIVE = _MediaError.MEDIA_ERR_NONE_ACTIVE || 0; +_MediaError.MEDIA_ERR_ABORTED = _MediaError.MEDIA_ERR_ABORTED || 1; +_MediaError.MEDIA_ERR_NETWORK = _MediaError.MEDIA_ERR_NETWORK || 2; +_MediaError.MEDIA_ERR_DECODE = _MediaError.MEDIA_ERR_DECODE || 3; +_MediaError.MEDIA_ERR_NONE_SUPPORTED = _MediaError.MEDIA_ERR_NONE_SUPPORTED || 4; +// TODO: MediaError.MEDIA_ERR_NONE_SUPPORTED is legacy, the W3 spec now defines it as below. +// as defined by http://dev.w3.org/html5/spec-author-view/video.html#error-codes +_MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = _MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED || 4; + +module.exports = _MediaError; |
