Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): Adding playAudioWhenScreenIsLocked for Android (CB-12146) #137

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
147 changes: 108 additions & 39 deletions src/android/AudioHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -57,8 +55,10 @@ 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
ArrayList<AudioPlayer> pausedForFocus; // Audio players that were paused when focus was lost
ArrayList<AudioPlayer> 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;

Expand All @@ -77,8 +77,7 @@ public class AudioHandler extends CordovaPlugin {
*/
public AudioHandler() {
this.players = new HashMap<String, AudioPlayer>();
this.pausedForPhone = new ArrayList<AudioPlayer>();
this.pausedForFocus = new ArrayList<AudioPlayer>();
this.paused = new ArrayList<AudioPlayer>();
}


Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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<AudioPlayer> remove = new ArrayList<AudioPlayer>();
for (AudioPlayer audio : this.paused) {
if(audio.isPlayAudioWhenScreenIsLocked()){
audio.resumePlaying();
remove.add(audio);
}
}
paused.removeAll(remove);
}



/**
* Get the the audio focus
*/
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
41 changes: 26 additions & 15 deletions src/android/AudioPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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<String>();

this.playAudioWhenScreenIsLocked = playAudioWhenScreenIsLocked;
}

private String generateTempFile() {
Expand Down