diff --git a/README.md b/README.md index e03f5c48..67172a2c 100644 --- a/README.md +++ b/README.md @@ -347,23 +347,28 @@ function playAudio(url) { } ``` -### 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 }) +### Parameters - __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.: + is locked or the app is in Background(Android). The standard value is true. + e.g.: var myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3"); myMedia.play({ playAudioWhenScreenIsLocked : true }); myMedia.setVolume('1.0'); + +### 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__: + If set to `true`, the state of the hardware mute button is ignored. > Note: To allow playback with the screen locked or background audio you have to add `audio` to `UIBackgroundModes` in the `info.plist` file. See [Apple documentation](https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW23). Also note that the audio has to be started before going to background. - __order of file search__: When only a file name or simple path is diff --git a/src/android/AudioHandler.java b/src/android/AudioHandler.java index 9e734c44..6a612ecc 100644 --- a/src/android/AudioHandler.java +++ b/src/android/AudioHandler.java @@ -18,29 +18,27 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova.media; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.PermissionHelper; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.media.AudioManager; -import android.media.AudioManager.OnAudioFocusChangeListener; -import android.net.Uri; -import android.os.Build; - import java.security.Permission; + import java.util.ArrayList; +import java.util.HashMap; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.LOG; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.HashMap; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.media.AudioManager.OnAudioFocusChangeListener; +import android.net.Uri; /** * This class called by CordovaActivity to play and record audio. @@ -57,8 +55,10 @@ public class AudioHandler extends CordovaPlugin { public static String TAG = "AudioHandler"; HashMap players; // Audio player object - ArrayList pausedForPhone; // Audio players that were paused when phone call came in - ArrayList pausedForFocus; // Audio players that were paused when focus was lost + ArrayList paused; // Audio players that were paused. Reasons: + boolean audioFocusLost = false; // paused when audiofocus was lost + boolean calling = false; // paused when calling + boolean activityFocusLost = false; // paused when activity got paused private int origVolumeStream = -1; private CallbackContext messageChannel; @@ -77,8 +77,7 @@ public class AudioHandler extends CordovaPlugin { */ public AudioHandler() { this.players = new HashMap(); - this.pausedForPhone = new ArrayList(); - this.pausedForFocus = new ArrayList(); + this.paused = new ArrayList(); } @@ -129,13 +128,20 @@ else if (action.equals("resumeRecordingAudio")) { else if (action.equals("startPlayingAudio")) { String target = args.getString(1); String fileUriStr; + boolean playAudioWhenScreenIsLocked=true; try { Uri targetUri = resourceApi.remapUri(Uri.parse(target)); fileUriStr = targetUri.toString(); } catch (IllegalArgumentException e) { fileUriStr = target; } - this.startPlayingAudio(args.getString(0), FileHelper.stripFileProtocol(fileUriStr)); + try { + JSONObject playArgs = new JSONObject(args.getString(2)); + playAudioWhenScreenIsLocked = playArgs.getBoolean("playAudioWhenScreenIsLocked"); + } catch (JSONException e) { + playAudioWhenScreenIsLocked = true; + } + this.startPlayingAudio(args.getString(0), FileHelper.stripFileProtocol(fileUriStr),playAudioWhenScreenIsLocked); } else if (action.equals("seekToAudio")) { this.seekToAudio(args.getString(0), args.getInt(1)); @@ -225,21 +231,21 @@ public Object onMessage(String id, Object data) { 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(); - } - } + calling = true; + pauseAll(); } // 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(); + if(!audioFocusLost){ + if (activityFocusLost) { + resumePlayingMarked(); + }else { + resumePlayingAll(); + } + } + calling = false; } } return null; @@ -250,14 +256,19 @@ else if ("idle".equals(data)) { //-------------------------------------------------------------------------- private AudioPlayer getOrCreatePlayer(String id, String file) { + return getOrCreatePlayer(id,file,true); + } + + private AudioPlayer getOrCreatePlayer(String id, String file, boolean playAudioWhenScreenIsLocked) { AudioPlayer ret = players.get(id); if (ret == null) { if (players.isEmpty()) { onFirstPlayerCreated(); } - ret = new AudioPlayer(this, id, file); + ret = new AudioPlayer(this, id, file, playAudioWhenScreenIsLocked); players.put(id, ret); } + ret.setPlayAudioWhenScreenIsLocked(playAudioWhenScreenIsLocked); return ret; } @@ -315,8 +326,8 @@ public void resumeRecordingAudio(String id) { * @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); + public void startPlayingAudio(String id, String file, boolean playAudioWhenScreenIsLocked) { + AudioPlayer audio = getOrCreatePlayer(id, file, playAudioWhenScreenIsLocked); audio.startPlaying(file); getAudioFocus(); } @@ -400,22 +411,56 @@ else if (output == 1) { } } - public void pauseAllLostFocus() { + /** + * This method pauses all AudioPlayers. + * */ + public void pauseAll() { for (AudioPlayer audio : this.players.values()) { if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal()) { - this.pausedForFocus.add(audio); + this.paused.add(audio); audio.pausePlaying(); } } } - public void resumeAllGainedFocus() { - for (AudioPlayer audio : this.pausedForFocus) { - audio.resumePlaying(); + /** + * This method pauses all AudioPlayers not marked with isPlayAudioWhenScreenIsLocked. + * */ + public void pauseAudiosNotMarked() { + for (AudioPlayer audio : this.players.values()) { + if (audio.getState() == AudioPlayer.STATE.MEDIA_RUNNING.ordinal() && !audio.isPlayAudioWhenScreenIsLocked()) { + this.paused.add(audio); + audio.pausePlaying(); + } } - this.pausedForFocus.clear(); } + /** + * Resume playing all paused audios + */ + public void resumePlayingAll() { + for (AudioPlayer audio : this.paused) { + audio.resumePlaying(); + } + paused.clear(); + } + + /** + * Resume playing all audios marked with isPlayAudioWhenScreenIsLocked + */ + public void resumePlayingMarked() { + ArrayList remove = new ArrayList(); + for (AudioPlayer audio : this.paused) { + if(audio.isPlayAudioWhenScreenIsLocked()){ + audio.resumePlaying(); + remove.add(audio); + } + } + paused.removeAll(remove); + } + + + /** * Get the the audio focus */ @@ -425,10 +470,18 @@ public void onAudioFocusChange(int focusChange) { case (AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) : case (AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) : case (AudioManager.AUDIOFOCUS_LOSS) : - pauseAllLostFocus(); + pauseAll(); + audioFocusLost = true; break; case (AudioManager.AUDIOFOCUS_GAIN): - resumeAllGainedFocus(); + if (!calling) { + if(activityFocusLost){ + resumePlayingMarked(); + }else{ + resumePlayingAll(); + } + } + audioFocusLost = false; break; default: break; @@ -552,6 +605,22 @@ else if(PermissionHelper.hasPermission(this, permissions[RECORD_AUDIO])) } } + + @Override + public void onResume(boolean multitasking) { + super.onResume(multitasking); + if (!(audioFocusLost||calling)) { + resumePlayingAll(); + } + activityFocusLost = false; + } + + @Override + public void onPause(boolean multitasking) { + activityFocusLost = true; + pauseAudiosNotMarked(); + super.onPause(multitasking); + } /** * Get current amplitude of recording. diff --git a/src/android/AudioPlayer.java b/src/android/AudioPlayer.java index 861421e7..9487181f 100644 --- a/src/android/AudioPlayer.java +++ b/src/android/AudioPlayer.java @@ -18,6 +18,18 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova.media; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedList; + +import org.apache.cordova.LOG; +import org.json.JSONException; +import org.json.JSONObject; + import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; @@ -26,19 +38,6 @@ Licensed to the Apache Software Foundation (ASF) under one import android.media.MediaRecorder; import android.os.Environment; -import org.apache.cordova.LOG; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.IOException; -import java.util.LinkedList; - /** * This class implements the audio playback and recording capabilities used by Cordova. * It is called by the AudioHandler Cordova class. @@ -93,17 +92,29 @@ public enum STATE { MEDIA_NONE, private boolean prepareOnly = true; // playback after file prepare flag private int seekOnPrepared = 0; // seek to this location once media is prepared - /** + private boolean playAudioWhenScreenIsLocked = true; // If set to false the playback will be paused when app is paused + + public boolean isPlayAudioWhenScreenIsLocked() { + return playAudioWhenScreenIsLocked; + } + + public void setPlayAudioWhenScreenIsLocked(boolean playAudioWhenScreenIsLocked) { + this.playAudioWhenScreenIsLocked = playAudioWhenScreenIsLocked; + } + + /** * Constructor. * * @param handler The audio handler object * @param id The id of this audio player */ - public AudioPlayer(AudioHandler handler, String id, String file) { + public AudioPlayer(AudioHandler handler, String id, String file, boolean playAudioWhenScreenIsLocked) { this.handler = handler; this.id = id; this.audioFile = file; this.tempFiles = new LinkedList(); + + this.playAudioWhenScreenIsLocked = playAudioWhenScreenIsLocked; } private String generateTempFile() {